This article will teach you how to integrate JWT (JSON Web Token) based on Shiro + springBoot SpringBoot, avoid various potholes

Source code: github.com/HowieYuan/s…

JWT

JSON Web Token (JWT) is a very lightweight specification. This specification allows us to use JWT to deliver secure and reliable information between the user and the server.

We use certain encoding to generate tokens and add some non-sensitive information into tokens to pass them on.

A full Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7 ItqpKViM

In this project, we stipulate that each request shall be made with token in the request header, and the permission shall be checked by token. If there is no token, it indicates that the current state is a tourist (or login interface, etc.).

JWTUtil

We use JWT utility class to generate our tokens. This utility class mainly has two methods of generating tokens and verifying tokens

When generating a token, specify the expiration time EXPIRE_TIME and the signature key SECRET, write the date and username into the token, and use the HS256 signature algorithm with the key to sign the token

Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWT.create()
   .withClaim("username"WithExpiresAt (date) // Create a new JWT and mark it with the given algorithm. Sign (algorithm);Copy the code

The database table

Each user has corresponding roles (user, admin) and permissions (Normal, VIP). The default permission of the user role is Normal, and the default permission of the admin role is VIP (of course, user can also be VIP).

The filter

In the previous article, we use the shiro default permissions for the interceptor Filter, and because of the integration of JWT, we need a custom Filter JWTFilter, JWTFilter inherited BasicHttpAuthenticationFilter, Part of the original method is rewritten

The filter mainly has three steps:

  1. Verify that the request header has a token((HttpServletRequest) request).getHeader("Token") ! = null
  2. If you have a token, execute Shiro’s login() method to commit the token to a Realm for verification; If there is no token, the current state is tourist state (or some other interface that does not require authentication)
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {// Determine whether the request header is included"Token"
        if (((HttpServletRequest) request).getHeader("Token")! ExecuteLogin (request, response); executeLogin(request, response);return true; } catch (Exception e) {//token error responseError(response, LLDB message ()); }} // If the Token is not present in the request header, it may be a login operation or a visitor state access, without checking the Token, and return directlytrue
        return true;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Token"); JWTToken jwtToken = new JWTToken(token); GetSubject (Request, Response).login(jwtToken); // Submit to realm for login. // If no exception is thrown, the login is successfultrue
        return true;
    }
Copy the code
  1. If an error occurs during the token verification, for example, the token verification fails, THE request is regarded as an authentication failure and redirected to/unauthorized/**

In addition, I put cross-domain support into this filter to deal with

Realm class

It’s still our custom Realm, but for those of you who don’t know anything about it, check out my last Shiro post

  • The identity authentication
if(username == null || ! JWTUtil.verify(token, username)) { throw new AuthenticationException("Token authentication failed!");
}
String password = userMapper.getPassword(username);
if (password == null) {
    throw new AuthenticationException("This user does not exist!");
}
int ban = userMapper.checkUserBanStatus(username);
if (ban == 1) {
    throw new AuthenticationException("This user has been banned!");
}
Copy the code

Get the sent token, check whether the token is valid, whether the user exists, and the user’s blocking status

  • Authority certification
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String role = usermapper. getRole(username); / / have the default permissions to each character String rolePermission = userMapper. GetRolePermission (username); // Each user can set a new permission String permission = usermapper.getPermission (username); Set<String> roleSet = new HashSet<>(); Set<String> permissionSet = new HashSet<>(); / / need to role, the permission encapsulation to Set as the info. The setRoles (), info. SetStringPermissions roleSet () parameters. The add (role); permissionSet.add(rolePermission); permissionSet.add(permission); // Set the role and permission of the user info.setroles (roleSet); info.setStringPermissions(permissionSet);Copy the code

Username obtained from token is used to check the role and permissions of the user from the database and save them into SimpleAuthorizationInfo

ShiroConfig configuration class

Set our custom filters and make all requests pass through our filters, except for the/Unauthorized /** we use to handle unauthenticated requests

@Bean public ShiroFilterFactoryBean factory(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); // Add your own Filter and name it JWT Map<String, Filter> filterMap = new HashMap<>(); // Set our custom JWT filter filtermap.put ("jwt", new JWTFilter()); factoryBean.setFilters(filterMap); factoryBean.setSecurityManager(securityManager); Map<String, String> filterRuleMap = new HashMap<>(); // All requests pass through our own JWT Filter filterRulemap.put ("/ * *"."jwt"); ** Do not pass JWTFilter filterRulemap.put ("/unauthorized/**"."anon");
    factoryBean.setFilterChainDefinitionMap(filterRuleMap);
    return factoryBean;
}
Copy the code

Permissions for RequiresRoles, @Requirespermissions

These two annotations are our main permission control annotations, such as

// Having an admin role can access @requiresRoles ("admin")
Copy the code
RequiresRoles(logical = logical. OR, value = {) @requiresRoles (logical = logical. OR, value = {)"user"."admin"})
Copy the code
RequiresPermissions(logical = logical. AND, value = {) @requirespermissions (logical = logical. AND, value = {"vip"."normal"})
Copy the code
// You have the user or admin role and VIP permission to access @getMapping ("/getVipMessage")
@RequiresRoles(logical = Logical.OR, value = {"user"."admin"})
@RequiresPermissions("vip")
public ResultMap getVipMessage() {
    return resultMap.success().code(200).message("Successfully obtained VIP information!");
}
Copy the code

When we write an interface with the above annotations, if the request does not carry a token or carries a token but permission authentication fails, an UnauthenticatedException will be reported. But I handled these exceptions centrally in the ExceptionController class

@ExceptionHandler(ShiroException.class)
public ResultMap handle401() {
    return resultMap.fail().code(401).message("You have no access!");
}
Copy the code

At this point, shiro-related exceptions are returned

{
    "result": "fail"."code": 401,
    "message": "You have no access!"
}
Copy the code

In addition to the above two annotations, @requiresAuthentication, @requiresuser, etc

Function implementation

User roles are divided into three categories: admin, user, and guest. By default, the admin user has the VIP permission, and the user user has the normal permission. When the user is upgraded to the VIP permission, the user can access the VIP page.

Specific implementation can see the source code (address given at the beginning)

landing

The login interface does not contain a token. If the login password and user name are verified correctly, the token is returned.

@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("User name error");
    } else if(! realPassword.equals(password)) {return resultMap.fail().code(401).message("Password error");
    } else {
        returnresultMap.success().code(200).message(JWTUtil.createToken(username)); }}Copy the code
{
    "result": "success"."code": 200,
    "message": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MjUxODQyMzUsInVzZXJuYW1lIjoiaG93aWUifQ.fG5Qs739Hxy_JjTdSIx_iiwaBD43aKF QMchx9fjaCRo"
}
Copy the code

Exception handling

// Catch Shiro exceptions @ExceptionHandler(ShiroException.class) public ResultMaphandle401() {
        return resultMap.fail().code(401).message("You have no access!"); } @ExceptionHandler(exception.class) public ResultMap globalException(HttpServletRequest Request, Throwable ex) {return resultMap.fail()
                .code(getStatus(request).value())
                .message("Access error, cannot access:" + ex.getMessage());
    }
Copy the code

Access control

  • RequiresRoles(logical = logical. OR, value = {“user”, “admin”})

    • VIP access plus@RequiresPermissions("vip")
  • AdminController (admin accessible) with @RequiresRoles(“admin”) on the interface

  • GuestController (Accessible to all) Does not perform permission processing

The test results