Project development, the background system is particularly important, recently want to make a set of direct use of the background management system.

Framework:

Shiro, as a security framework, Shiro implements login, logout, authentication, authorization, and session management.

JWT, Json Web Token is responsible for authentication when accessing an interface.

Vue, the front-end framework, Clone someone’s github template because it’s not front-end development. Very well written and easy to use. Github.com/PanJiaChen/…

The front-end code is a copy, but many places also Google, or very troublesome, here not to repeat. Let’s clean up some of the back-end Shiro+JWT code logic.

Shiro

@Configuration
public class ShiroConfig {
 / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | ha ha | = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * /
    /** * Verification code Verifies filter **@return* /
    @Bean(name = "captchaValidate")
    public CaptchaValidateFilter captchaValidate(a) {
        return new CaptchaValidateFilter();
    }
    /**
     * jwt-filter
     *
     * @return* /
    @Bean(name = "jwt")
    public JwtFilter jwtFilter(a) {
        return new JwtFilter();
    }

    /** * Verify the account password during login@return* /
    @Bean
    public GeneralCredentialsMatcher generalCredentialsMatcher(a) {
        return new GeneralCredentialsMatcher();
    }

    /** * Account authentication, permission authentication **@return* /
    @Bean
    public UserRealm myRealm(a) {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(generalCredentialsMatcher());
        return userRealm;
    }


    @Bean(name = "shiroFilterChainDefinition")
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(a) {
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition("/api/user/login"."captchaValidate");
        definition.addPathDefinition("/api/**"."jwt");
        return definition;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(a) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        manager.setSessionManager(defaultWebSessionManager());
        manager.setCacheManager(redisCacheManager());
        return manager;
    }
    @Bean
    public RedisManager redisManager(a) {
        return new RedisManager();
    }

    @Bean
    public RedisCacheManager redisCacheManager(a) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(a) {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        return defaultWebSessionManager;
    }
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(a){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(a) {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(a) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        returnauthorizationAttributeSourceAdvisor; }}Copy the code
  • The custom filter captchaValidate() is responsible for processing the verification code, which is triggered when the login interface is clicked.
  • JwtFilter () inherited shiro BasicHttpAuthenticationFilter class, rewrite the isAccessAllowed and onAccessDenied method. The front end will send the OPTIONS detection method to confirm that the server allows cross-domain. Here’s a quick way to let the request pass without being intercepted and verify that if it’s not an OPTIONS request, it contains JWT header information
  • GeneralCredentialsMatcher () is responsible for handling the login authentication, query the database to verify the user name password.
  • MyRealm () extends to shiro AuthorizingRealm, overwriting doGetAuthenticationInfo to create and store user information. Override the doGetAuthorizationInfo method to query and save user role permission information.
  • ShiroFilterChainDefinition configure shiro filter () invocation chain, in the call the login interface triggered when a captcha validation of the filter, and other all interfaces will trigger the JWT filter.
  • SecurityManager () configuration DefaultWebSecurityManager, redis to save the session session information.
  • Redis configuration, enable Shiro annotation configuration.

The login

@PostMapping(value = "/user/login")
    public ResponseEntity<ApiResponse<LoginResponse>> login(@RequestBody @Validated LoginRequest loginRequest) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(loginRequest.getUsername(), loginRequest.getPassword());
        token.setRememberMe(true);
        subject.login(token);
        LoginUser user = subject.getPrincipals().oneByType(LoginUser.class);
        String sessionId = subject.getSession().getId().toString();
        String jwtToken = JwtTokenUtils.createToken(TokenParam.builder()
                .key(JwtTokenUtils.SESSION_KEY)
                .value(sessionId)
                .build());
        LoginResponse loginResponse = LoginResponse.builder()
                .name(user.getUserName())
                .token(jwtToken)
                .build();
        log.info("login success");
        return super.getApiResponseResponseEntity(loginResponse);
    }
Copy the code

The login interface is not intercepted. First, get the Subject instance of the current thread, encapsulate the UsernamePasswordToken object with interface parameters, and call subject.login(token). After successful invocation, the sessionId after successful login is obtained as part of JWT, and then the data is encapsulated and returned to the front end. At this point the session is stored in Redis.

Securityutils.getsubject ().getPrincipal() is null. This is because Securityutils.getSubject fetches the Subject object stored in ThreadLocal by the current thread. On the other hand, if the project is separated from the front and back end, each request will not be the same as the previous Thread, which will result in null.

If annotation validation is configured on other methods, such as @requiresRoles (“admin”), then annotation validation is performed before other Shro-filter configurations, Securityutils.getsubject () is also called during annotation validation, and the same null problem occurs.

To solve the problem of null securityutils.getSubject ().getPrincipal(), add the Spring Interceptor. This is done in order to look up shiro’s logon configured cache in Redis via the sessionID in the token before annotating the method.

public class ShiroSubjectInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String authorization = request.getHeader("Authorization");
        String sessionId = JwtTokenUtils.getPayloadMapValue(authorization, JwtTokenUtils.SESSION_KEY);
        if(StringUtils.isBlank(sessionId)) {
            ApiResponse apiResponse = new ApiResponse();
            apiResponse.setSubCode(ApiResponseStatusCodeEnum.TOKEN_EXPIRED.getSubCode());
            apiResponse.setSubMessage(ApiResponseStatusCodeEnum.TOKEN_EXPIRED.getMessage());
            response.getWriter().write(GsonUtils.getGsonWithOutConfig().toJson(apiResponse));
            response.flushBuffer();
            return false;
        }
        Subject subject = new Subject.Builder().sessionId(sessionId).buildSubject();
        ThreadContext.bind(subject);
        return true; }}Copy the code

Subject subject = new Subject.Builder().sessionId(sessionId).buildSubject(); Fetch the cache from Redis. ThreadContext.bind(subject); Bind to the ThreadLocal of the current thread. That solves the above problem.

vue

Front end, about a few functions, the left menu bar according to the back-end permissions to load dynamically, some basic permissions control, role control, some add, delete, change and check. Login logs and operation logs will be improved in the future.

page

Github:github.com/libinjimubo…