Jwtutil
We use JWT's tool class to generate our tokens, a tool class that has two ways to generate tokens and check tokens.
When generating tokens, specify token expiration time EXPIRE_TIME
and signing key SECRET
, then write date and username to token, and sign with the HS256 signature algorithm with the key
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);Algorithm algorithm = Algorithm.HMAC256(SECRET);JWT.create() .withClaim("username", username) //到期时间 .withExpiresAt(date) //创建一个新的JWT,并使用给定的算法进行标记 .sign(algorithm);
Database tables
Role: Character; permission: permission; ban: Seal number status
Each user has a corresponding role (user,admin), Permissions (NORMAL,VIP), and the user role default permissions are normal, Admin role default permissions are VIP (of course, user can also be VIP)
Filter filters
In the previous article, we used the Shiro default permission intercept filter, and because of the integration of JWT, we need to customize our own filters Jwtfilter,jwtfilter inherit Basichttpauthenticationfilter, And part of the original method was rewritten
The filter has three main steps:
- Verify that the request header has token
((HttpServletRequest) request).getHeader("Token") != null
If there is token, execute the login () method of Shiro, submit token to Realm for inspection, if there is no token, indicate the current status is visitor status (or some other interface that does not require authentication)
@Overrideprotected boolean isaccessallowed (ServletRequest request, servletresponse response, Object Mappedvalue Throws Unauthorizedexception {//determines if the requested header is with "token" if (((httpservletrequest) request). GetHeader ("token")! = nul L) {//If present, enter the Executelogin method to perform the login, check that the token is correct try {executelogin (request, response); return true; } catch (Exception e) {//token error Responseerror (response, E.getmessage ()); }}//If the request header does not have token, it may be a login operation or visitor state access, without checking tokens, directly returning true return true;} @Overrideprotected boolean executelogin (ServletRequest request, servletresponse response) throws Exception {Httpservle Trequest httpservletrequest = (httpservletrequest) request; String token = Httpservletrequest.getheader ("token"); Jwttoken Jwttoken = new Jwttoken (token); Submit to Realm for login, and if wrong he throws an exception and is captured Getsubject (request, response). Login (Jwttoken); If no exception is thrown, the login succeeds, return true return true;}
- If an error occurs during token validation, such as a token check failure, then I will treat the request as if authentication does not pass, then redirect to
/unauthorized/**
In addition, I put cross-domain support in the filter to handle
Realm class
It's still our custom Realm, and for this piece you can read my previous article on Shiro.
Identity verification
if (username == null || !JWTUtil.verify(token, username)) {throw new AuthenticationException("token认证失败!");}String password = userMapper.getPassword(username);if (password == null) {throw new AuthenticationException("该用户不存在!");}int ban = userMapper.checkUserBanStatus(username);if (ban == 1) {throw new AuthenticationException("该用户已被封号!");}
Get the token, check if token is valid, whether the user exists, and the user's seal number
- Authority authentication
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//获得该用户角色String role = userMapper.getRole(username);//每个角色拥有默认的权限String rolePermission = userMapper.getRolePermission(username);//每个用户可以设置新的权限String permission = userMapper.getPermission(username);Set<String> roleSet = new HashSet<>();Set<String> permissionSet = new HashSet<>();//需要将 role, permission 封装到 Set 作为 info.setRoles(), info.setStringPermissions() 的参数roleSet.add(role);permissionSet.add(rolePermission);permissionSet.add(permission);//设置该用户拥有的角色和权限info.setRoles(roleSet);info.setStringPermissions(permissionSet);
Using tokens obtained in the username, from the database to find the user's own roles, permissions, stored in Simpleauthorizationinfo
Shiroconfig Configuration Class
Set up our custom filter and make all requests through our filters, in addition to those we use to process unauthenticated requests/unauthorized/**
@Beanpublic ShiroFilterFactoryBean factory(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); // 添加自己的过滤器并且取名为jwt Map<String, Filter> filterMap = new HashMap<>(); //设置我们自定义的JWT过滤器 filterMap.put("jwt", new JWTFilter()); factoryBean.setFilters(filterMap); factoryBean.setSecurityManager(securityManager); Map<String, String> filterRuleMap = new HashMap<>(); // 所有请求通过我们自己的JWT Filter filterRuleMap.put("/**", "jwt"); // 访问 /unauthorized/** 不通过JWTFilter filterRuleMap.put("/unauthorized/**", "anon"); factoryBean.setFilterChainDefinitionMap(filterRuleMap); return factoryBean;}
Permission control annotation @RequiresRoles, @RequiresPermissions
These two annotations are for our main permission control annotations, such as
// 拥有 admin 角色可以访问@RequiresRoles("admin")
// 拥有 user 或 admin 角色可以访问@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})
// 拥有 vip 和 normal 权限可以访问@RequiresPermissions(logical = Logical.AND, value = {"vip", "normal"})
// 拥有 user 或 admin 角色,且拥有 vip 权限可以访问@GetMapping("/getVipMessage")@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})@RequiresPermissions("vip")public ResultMap getVipMessage() { return resultMap.success().code(200).message("成功获得 vip 信息!");}
When we write the interface has the above annotations, if the request does not have token or with token but the permission authentication does not pass, it will report unauthenticatedexception exception, but I am in Exceptioncontroller Class handles these exceptions in a centralized
@ExceptionHandler(ShiroException.class)public ResultMap handle401() { return resultMap.fail().code(401).message("您没有权限访问!");}
At this point, a Shiro-related exception is returned
{ "result": "fail", "code": 401, "message": "您没有权限访问!"}
In addition to the above two types, there are @RequiresAuthentication, @RequiresUser and other annotations
function implementation
User roles are divided into three categories, admin admin, normal user, visitor guest;admin default permission is vip,user default permission is normal, when user upgrade to VIP permissions, you can access the VIP permissions of the page.
The implementation can see the source code (the address is given at the beginning)
Landing
Login interface without token, when the login password, the user name verification is correct after returning token.
@PostMapping("/login")public ResultMap login(@RequestParam("username") String username, @RequestParam("password") String password) { String realPassword = userMapper.getPassword(username); if (realPassword == null) { return resultMap.fail().code(401).message("用户名错误"); } else if (!realPassword.equals(password)) { return resultMap.fail().code(401).message("密码错误"); } else { return resultMap.success().code(200).message(JWTUtil.createToken(username)); }}
{ "result": "success", "code": 200, "message": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MjUxODQyMzUsInVzZXJuYW1lIjoiaG93aWUifQ.fG5Qs739Hxy_JjTdSIx_iiwaBD43aKFQMchx9fjaCRo"}
Exception handling
// 捕捉shiro的异常 @ExceptionHandler(ShiroException.class) public ResultMap handle401() { return resultMap.fail().code(401).message("您没有权限访问!"); } // 捕捉其他所有异常 @ExceptionHandler(Exception.class) public ResultMap globalException(HttpServletRequest request, Throwable ex) { return resultMap.fail() .code(getStatus(request).value()) .message("访问出错,无法访问: " + ex.getMessage()); }
Permission control
Usercontroller (user or admin can access)
On the interface.@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})
- VIP Privileges
Plus@RequiresPermissions("vip")
Admincontroller (Admin can access)
On the interface.@RequiresRoles("admin")
- Guestcontroller (Everyone can access)
Do not do permission handling
Test results
Springboot+shiro+jwt