This is the 9th day of my participation in Gwen Challenge

A few days ago, WHEN I was explaining Shiro to everyone, some backstage friends came to me and left a message saying, “Generally, shiro and JWT do identity and permission verification, right? Can you explain JWT again? “Today ah Q will tell you about Shiro integration JWT to do permissions check it.

JWT, which stands for Json Web Token, is an open jSON-based standard implemented to pass declarations between network application environments. The token is designed to be compact and secure, especially suitable for single sign-on (SSO) scenarios in distributed sites. The JWT declaration is generally used to pass authenticated user identity information between the identity provider and the service provider to obtain resources from the resource server, and to add some additional declaration information necessary for other business logic. The token can also be used directly for authentication or can be encrypted. To put it more generally, a previous session needs to store user information on the server in order to distinguish which user sent the request, consuming server resources. And as the number of users increases, it is bound to expand the server and adopt distributed system, so session may not be suitable, and the JWT we talk about today solves the single sign-on problem well, and it is easy to solve the problem of session sharing.

Without further ado, let’s go straight to the integration tutorial (this installment is based on shiro’s previous installment) :

Introduce JWT dependency packages into POM files

<dependency>
	<groupId>com.auth0</groupId>
	<artifactId>java-jwt</artifactId>
	<version>3.7. 0</version>
</dependency>
Copy the code

Write a utility class for generating and validating signatures

public class JwtUtil {

    //JWT-account
    public static final String ACCOUNT = "username";
    //JWT-currentTimeMillis
    public final static String CURRENT_TIME_MILLIS = "currentTimeMillis";
    // Valid for 2 hours
    public static final long EXPIRE_TIME = 2 * 60 * 60 * 1000L;
    / / the secret key
    public static final String SECRET_KEY = "shirokey";


    /** * Generate signature return token **@param account
     * @param currentTimeMillis
     * @return* /
    public static String sign(String account, String currentTimeMillis) {
        // The account is encrypted with the JWT private key
        String secret = account + SECRET_KEY;
        // Expiration time, in milliseconds, is valid from the current time to the next 20 minutes
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        // Use HMAC256 encryption
        Algorithm algorithm = Algorithm.HMAC256(secret);

        return JWT.create()
                .withClaim(ACCOUNT, account)
                .withClaim(CURRENT_TIME_MILLIS, currentTimeMillis)
                .withExpiresAt(date)
                // Create a new JWT and mark it with the given algorithm
                .sign(algorithm);
    }

    /** * Check whether the token is correct **@param token
     * @return* /
    public static boolean verify(String token) {
        String secret = getClaim(token, ACCOUNT) + SECRET_KEY;
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm)
                .build();
        verifier.verify(token);
        return true;
    }

    /** * Get the Token information without secret decryption@param token
     * @param claim
     * @return* /
    public static String getClaim(String token, String claim) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(claim).asString();
        } catch (JWTDecodeException e) {
            return null; }}}Copy the code

3. Encapsulate your own token for later verification of the token type

public class JwtToken implements AuthenticationToken {

    private final String token;

    public JwtToken(String token){
        this.token = token;
    }

    @Override
    public Object getPrincipal(a) {
        return token;
    }

    @Override
    public Object getCredentials(a) {
        return token;
    }
Copy the code

4. We need to create tokens at login

// Login processing in service
@Override
public UserTokenDTO login(UserTokenDTO userInfo) {
    // The user who obtains the username and password from the database
    SysUserInfo uInfo = userInfoMapper.getUserByLogin(userInfo.getName());
    if (null == uInfo) {
        // User information does not exist
        throw new BusinessException(CommonResultStatus.USERNAME_ERROR);
    } else if(! userInfo.getPassword().equals(uInfo.getPassword())) {// The password is incorrect
        throw new BusinessException(CommonResultStatus.PASSWORD_ERROR);
    }
    / / generated jwtToken
    userInfo.setToken(JwtUtil.sign(userInfo.getName(),String.valueOf(System.currentTimeMillis())));
    return userInfo;
}
Copy the code

5. Tokens are resolved in other requests that require login to access, so we need to customize filters

public class JwtFilter extends AccessControlFilter {

    // Set the name of the field to be passed in the request header
    protected static final String AUTHORIZATION_HEADER = "Access-Token";

    /** * indicates whether access is allowed, mappedValue is the interceptor parameter part of the [urls] configuration, and returns true if access is allowed, false otherwise@author cheetah
     * @date 2020/11/24
     * @param request:
      * @param response:
      * @param mappedValue:
     * @return: boolean
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }

    /** * indicates whether the interceptor instance has been processed when the access is denied. * If true, processing needs to continue. * If false, the interceptor instance has been processed and will be returned@author cheetah
     * @date 2020/11/24
     * @param request:
      * @param response:
     * @return: boolean
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        // Resolve cross-domain issues
        if(HttpMethod.OPTIONS.toString().matches(req.getMethod())) {
            return true;
        }
        if (isLoginAttempt(request, response)) {
            // Generate a JWT token
            JwtToken token = new JwtToken(req.getHeader(AUTHORIZATION_HEADER));
            // Delegate to Realm for validation
            try {
                // Calling logon goes through the authentication methods in Realm
                getSubject(request, response).login(token);
                return true;
            } catch (Exception e) {
            }
        }else{
            throw new BusinessException(CommonResultStatus.LOGIN_ERROR);
        }
        
        return false;
    }



    /** * Check whether there is a header argument *@author cheetah
     * @date 2020/11/24
     * @param request:
      * @param response:
     * @return: boolean
     */
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(AUTHORIZATION_HEADER);
        returnauthorization ! =null; }}Copy the code

When the subjection. login(token) method is called in the filter, the doGetAuthenticationInfo(AuthenticationToken token) method in the custom Realm is used to authenticate the identity

@Slf4j
public class JwtRealm extends AuthorizingRealm {

    @Autowired
    private UserInfoService userService;

    // Verify that it is its own token type
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /** * Authentication *@author cheetah
     * @date 2020/11/25
     * @param token:
     * @return: org.apache.shiro.authc.AuthenticationInfo
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String credentials = (String) token.getCredentials();
        String username = null;
        try {
            / / JWT authentication token
            boolean verify = JwtUtil.verify(credentials);
            if(! verify) {throw new AuthenticationException("Token check is incorrect");
            }
            username = JwtUtil.getClaim(credentials, JwtUtil.ACCOUNT);
        } catch (Exception e) {
            throw new BusinessException(CommonResultStatus.TOKEN_CHECK_ERROR,e.getMessage());
        }

        // Give AuthenticatingRealm to use CredentialsMatcher for password matching. If not, use the default SimpleCredentialsMatcher
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                username, / / user name
                credentials, / / credentials
                getName()  //realm name
        );
        return authenticationInfo;
    }

    /** * permission check **@author cheetah
     * @date 2020/11/25
     * @param principals:
     * @return: org.apache.shiro.authz.AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// String username = principals.toString();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // Role permissions are not added
// authorizationInfo.setRoles(userService.getRoles(username));
// authorizationInfo.setStringPermissions(userService.queryPermissions(username));
        returnauthorizationInfo; }}Copy the code

Next we need to modify the ShiroConfig file to give the custom Filter and Realm to the SecurityManager

/** This class is longer, only part of the important code, the rest of the code can be in the public account "AH Q said" reply "JWT" to obtain the source **/
@Configuration
@Slf4j
public class ShiroConfig {

    /** * Create ShiroFilterFactoryBean *@author cheetah
     * @date 2020/11/21
     * @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // The SecurityManager must be set
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // Set shiro's built-in filter
        Map<String, Filter> filters = new LinkedHashMap<>();
        // Add a custom filter: Filters only the interfaces that need to be logged in
        filters.put("authc".new JwtFilter());
        // Add a custom filter to verify permissions
// filters.put("roles", new CustomRolesOrAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filters);

        // setLoginUrl If you do not set the value, the "/login.jsp" page or "/login" mapping in the root directory of the Web project will be automatically searched by default
        shiroFilterFactoryBean.setLoginUrl("/adminLogin/login");
        // Set the unrestricted jump url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/notAuth");

        // Set interceptor
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // Visitors, development permission
        filterChainDefinitionMap.put("/guest/**"."anon");
        // user, need role permission "user"
        filterChainDefinitionMap.put("/user/**"."roles[user]");
// filterChainDefinitionMap.put("/productInfo/**", "roles[user]");
        // Administrator, need role permission "admin"
        filterChainDefinitionMap.put("/admin/**"."roles[admin]");
        // Open the login interface
        filterChainDefinitionMap.put("/adminLogin/login"."anon");
        // All other interfaces are blocked
        // This line of code must be placed at the end of all permission Settings, otherwise all urls will be blocked
        filterChainDefinitionMap.put("/ * *"."authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        log.info("-------Shiro interceptor Factory class injection successful -----------");
        return shiroFilterFactoryBean;
    }

    /** * Inject security manager *@author cheetah
     * @date 2020/11/21
     * @return: java.lang.SecurityManager
     */
    @Bean
    public DefaultWebSecurityManager securityManager(JwtRealm jwtRealm, SubjectFactory subjectFactory, SessionManager sessionManager, CacheManager cacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(jwtRealm);

        // Close shiro's built-in session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        securityManager.setSubjectFactory(subjectFactory);
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(cacheManager);
        return securityManager;
    }

    /** * JWT authentication and permission verification *@author cheetah
     * @date 2020/11/24
     * @return: com.cheetah.shiroandjwt.jwt.JwtRealm
     */
    @Bean
    public JwtRealm jwtRealm(a) {
        JwtRealm jwtRealm = new JwtRealm();
        jwtRealm.setAuthenticationCachingEnabled(true);
        jwtRealm.setAuthorizationCachingEnabled(true);
        returnjwtRealm; }}Copy the code

Important: Leave custom realms to SecurityManage and close Shiro’s built-in session

Next we start the program to verify: when we are not logged in, the request failed, need to log in first

The token information is obtained after the login. Procedure

The information is available when you Access it with an “access-token” header.

That’s all for today, if you have different ideas or better ideas, please contact Qingqing-4132, Ah Q is looking forward to your arrival! Reply to “JWT” for this source!