Shiro統合JWT無状態認証メカニズム(Token)

17427 ワード

まず大まかな手順を述べる
  • JWTツールクラス、これはネットで探しています.
  • はrealmをカスタマイズし、AuthorizingRealmを継承し、認証と認可の2つの方法
  • を書き換える.
  • カスタムfilter、BasichttpAuthenticationFilter
  • を継承
  • JWTを使用しているので、セッションを直接無効にします.
  • ポイント、RedisにはJWTToken情報(JWTの制御可能性)
  • が保存されている
    ユーザーがログインした後、jwtToken(tokenに5分などの期限切れの時間が保存されている)に戻り、同時にtokenをredisに保存し(30分などの自動削除時間を設定)、その後、ユーザーはこのTokenを持って他のインタフェースにアクセスし、redisにこのtokenがない場合、tokenはすでに失効し、tokenが5分を超え(期限切れ)てredis内のこのtokenがまだ存在する場合、tokenを再生成してユーザーに返し、redis内のtokenを更新すると、tokenの時間が5分延長されます.Redisにもtokenが保存されているのでTユーザー、統計オンラインユーザーなどの機能を実現できます
     
    まずサードパーティのjarパッケージを追加します(githubにコードが付いているので貼らない)
    https://github.com/gemingyi/shiro_demo
    一、JWTツールクラス(ネットで探した)
    @Component
    public class JWTUtil {
        //token    
        private static String tokenExpireTime ;
    
        @Value("${jwt.tokenExpireTime}")
        public void setTokenExpireTime(String tokenExpireTime) {
            JWTUtil.tokenExpireTime = tokenExpireTime;
        }
    
    
        /**
         *   token    
         *
         * @param token    
         * @param secret      
         * @return     
         */
        public static Map verify(String token, String username, String secret) {
            Map result = new HashMap(2);
            try {
                Algorithm algorithm = Algorithm.HMAC256(secret);
                JWTVerifier verifier = JWT.require(algorithm)
                        .withClaim("userName", username)
                        .build();
                DecodedJWT jwt = verifier.verify(token);
                result.put("isSuccess", true);
                result.put("ex", null);
    //            return true;
            } catch (Exception exception) {
                result.put("isSuccess", false);
                result.put("exception", exception);
    //        return false;
            }
            return result;
        }
    
    
    
        /**
         *   token      secret      
         *
         * @return token       
         */
        public static String getUsername(String token) {
            try {
                DecodedJWT jwt = JWT.decode(token);
                return jwt.getClaim("userName").asString();
            } catch (JWTDecodeException e) {
                e.printStackTrace();
                return null;
            }
        }
    
    
        /**
         *     ,30min   
         *
         * @param username    
         * @param secret        
         * @return    token
         */
        public static String sign(String username, String secret) {
            try {
                //token    
                Date date = new Date(System.currentTimeMillis() + (Long.parseLong(tokenExpireTime) * 60 * 1000));
                //  MD5  
    //            Object md5Password = new SimpleHash("MD5", secret, username, 2);
    //            Algorithm algorithm = Algorithm.HMAC256(String.valueOf(md5Password));
                Algorithm algorithm = Algorithm.HMAC256(secret);
                //   username  
                return JWT.create()
                        .withClaim("userName", username)
                        .withExpiresAt(date)
                        .sign(algorithm);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    }
  • はrealmをカスタマイズし、AuthorizingRealmを継承し、認証と認可の2つの方法
  • を書き換える.
    public class MyRealm extends AuthorizingRealm {
        @Autowired
        private IUserService userService;
    
        @Autowired
        RedisTemplate redisTemplate;
    
        /**
         *   !,       ,  Shiro   
         */
        @Override
        public boolean supports(AuthenticationToken token) {
            return token instanceof JWTToken;
        }
    
    
        /**
         *   
         *
         * @param principals
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String userName = JWTUtil.getUsername(principals.toString());
            SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
            Map map = null;
            try {
                map = this.userService.getRolesAndPermissionsByUserName(userName);
                auth.setRoles((Set) map.get("allRoles"));
                auth.setStringPermissions((Set) map.get("allPermissions"));
            } catch (Exception e) {
                e.printStackTrace();
            }
            return auth;
        }
    
    
        /**
         *   
         *
         * @param auth
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
            String token = (String) auth.getCredentials();
            //     userName,          
            String userName = JWTUtil.getUsername(token);
            User vo = this.userService.getUserByUserName(userName);
            String redisUserInfo = (String) redisTemplate.opsForValue().get("token_jwt_" + userName);
    
            Map result = JWTUtil.verify(token, userName, vo.getPassword());
            Exception exception = (Exception) result.get("exception");
    
            if (vo == null) {
                throw new UnknownAccountException("      !");
            } else if (vo.getLock() == null || vo.getLock().equals(1)) {
                throw new UnknownAccountException("       !");
            } else if (exception != null && exception instanceof SignatureVerificationException) {
                throw new AuthenticationException("Token  (Token incorrect.)!");
            } else if (exception != null && exception instanceof TokenExpiredException) {
                throw new AuthenticationException("Token   (Token expired.)!");
                // T
            } else if(StringUtils.isEmpty(redisUserInfo)){
                throw new AuthenticationException("Token   (Token invalid.)!");
            }else {
                AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(token, token, vo.getUserName());
                return authcInfo;
            }
        }
    }
    
     、   filter,  BasicHttpAuthenticationFilter
    
    @Component
    public class JWTFilter extends BasicHttpAuthenticationFilter {
    
        @Value("${jwt.anonymous.urls}")
        private String anonymousStr;
    
        @Autowired
        RedisTemplate redisTemplate;
        @Autowired
        private IUserService userService;
    
    
        @Override
        protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
            String contextPath = WebUtils.getPathWithinApplication(WebUtils.toHttp(servletRequest));
            if (!StringUtils.isEmpty(anonymousStr)) {
                String[] anonUrls = anonymousStr.split(",");
                //      url
                for (int i = 0; i < anonUrls.length; i++) {
                    if (contextPath.contains(anonUrls[i])) {
                        return true;
                    }
                }
            }
    
    
            //     token
            AuthenticationToken token = this.createToken(servletRequest, servletResponse);
            if (token.getPrincipal() == null) {
                handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), CodeAndMsgEnum.UNAUTHENTIC.getMsg());
                return false;
            } else {
                try {
                    this.getSubject(servletRequest, servletResponse).login(token);
                    return true;
                } catch (Exception e) {
                    String msg = e.getMessage();
                    //token  
                    if (msg.contains("incorrect")) {
                        handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), msg);
                        return false;
                        //token  
                    } else if (msg.contains("expired")) {
                        //    token
                        if (this.refreshToken(servletRequest, servletResponse)) {
                            return true;
                        } else {
                            handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), "token   ,     ");
                            return false;
                        }
    
                    }
                    handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), msg);
                    return false;
                }
            }
    
        }
    
    
        /**
         *    AccessToken  ,    RefreshToken    ,        AccessToken       
         *
         * @param servletRequest
         * @param servletResponse
         * @return
         */
        private boolean refreshToken(ServletRequest servletRequest, ServletResponse servletResponse) {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            //  header,tokenStr
            String oldToken = request.getHeader("Authorization");
            String userName = JWTUtil.getUsername(oldToken);
            String key = CommonConstant.JWT_TOKEN + userName;
            //  redis tokenStr
            String redisUserInfo = (String) redisTemplate.opsForValue().get(key);
            if (redisUserInfo != null) {
                if (oldToken.equals(redisUserInfo)) {
                    User vo = this.userService.getUserByUserName(userName);
                    //    token(  )
                    String newTokenStr = JWTUtil.sign(vo.getUserName(), vo.getPassword());
                    JWTToken jwtToken = new JWTToken(newTokenStr);
                    userService.addTokenToRedis(userName, newTokenStr);
                    SecurityUtils.getSubject().login(jwtToken);
                    response.setHeader("Authorization", newTokenStr);
                    return true;
                }
            }
            return false;
        }
    
    
        /**
         * token   
         *
         * @param response
         * @param code
         * @param msg
         */
        private void handler401(ServletResponse response, int code, String msg) {
            try {
                HttpServletResponse httpResponse = (HttpServletResponse) response;
                httpResponse.setStatus(HttpStatus.OK.value());
                response.setContentType("application/json;charset=utf-8");
                httpResponse.getWriter().write("{\"code\":" + code + ", \"msg\":\"" + msg + "\"}");
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    
    
    
        /**
         *     
         *
         * @param request
         * @param response
         * @return
         * @throws Exception
         */
        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setHeader("Access-control-Allow-Origin", "*");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", "Authorization,Origin,X-Requested-With,Content-Type,Accept");
    
            //           option  ,     option          
            if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
                httpServletResponse.setStatus(HttpStatus.OK.value());
                return false;
            }
            return super.preHandle(httpServletRequest, httpServletResponse);
    
        }
    
    
        @Override
        protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String token = request.getHeader("Authorization");
            return new JWTToken(token);
        }
    }
    
  • セッションを無効にする
  • public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
        @Override
        public Subject createSubject(SubjectContext context) {
            //   session
            context.setSessionCreationEnabled(false);
            return super.createSubject(context);
        }
    }
    
  • shiro構成クラス
  • @Configuration
    public class ShiroConfiguration {
        @Bean(name = "myRealm")
        public MyRealm myRealm() {
            MyRealm myShiroRealm = new MyRealm();
    //        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            myShiroRealm.setCachingEnabled(true);
    //        myShiroRealm.setCacheManager(redisCacheManager());
            return myShiroRealm;
        }
    
    
        @Bean(name = "subjectFactory")
        public StatelessDefaultSubjectFactory subjectFactory() {
            StatelessDefaultSubjectFactory statelessDefaultSubjectFactory = new StatelessDefaultSubjectFactory();
            return statelessDefaultSubjectFactory;
        }
    
    
    
        @Bean(name = "sessionManager")
        public DefaultSessionManager sessionManager() {
            DefaultSessionManager sessionManager = new DefaultSessionManager();
            sessionManager.setSessionValidationSchedulerEnabled(false);
            return sessionManager;
        }
    
    
        @Bean(name = "defaultSessionStorageEvaluator")
        public DefaultSessionStorageEvaluator defaultSessionStorageEvaluator () {
            DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
            defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
            return defaultSessionStorageEvaluator;
        }
    
    
        @Bean(name = "subjectDAO")
        public DefaultSubjectDAO subjectDAO(@Qualifier("defaultSessionStorageEvaluator")DefaultSessionStorageEvaluator defaultSessionStorageEvaluator) {
            DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
            defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
            return defaultSubjectDAO;
        }
    
    
        @Bean(name = "securityManager")
        public SecurityManager securityManager(@Qualifier("myRealm")MyRealm myRealm, @Qualifier("subjectDAO")DefaultSubjectDAO
                subjectDAO, @Qualifier("sessionManager")DefaultSessionManager sessionManager, @Qualifier("subjectFactory")StatelessDefaultSubjectFactory subjectFactory) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myRealm);
            securityManager.setSubjectDAO(subjectDAO);
            securityManager.setSubjectFactory(subjectFactory);
            securityManager.setSessionManager(sessionManager);
            return securityManager;
        }
    
    
        @Bean(name = "jwtFilter")
        public JWTFilter jwtFilter() {
            return new JWTFilter();
        }
    
    
        @Bean
        public FilterRegistrationBean delegatingFilterProxy(){
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            DelegatingFilterProxy proxy = new DelegatingFilterProxy();
            proxy.setTargetFilterLifecycle(true);
            proxy.setTargetBeanName("shiroFilter");
            filterRegistrationBean.setFilter(proxy);
            return filterRegistrationBean;
        }
    
    
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager securityManager, @Qualifier("jwtFilter")JWTFilter jwtFilter) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            //
            Map filters = new HashedMap(2);
            filters.put("jwtFilter", jwtFilter);
            shiroFilterFactoryBean.setFilters(filters);
            //   
            Map filterChainDefinitionMap = new LinkedHashMap();
            filterChainDefinitionMap.put("/**", "jwtFilter");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
    
        @Bean(name = "advisorAutoProxyCreator")
        public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            advisorAutoProxyCreator.setProxyTargetClass(true);
            return advisorAutoProxyCreator;
        }
    
    
        @Bean(name = "authorizationAttributeSourceAdvisor")
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
    

    コントロール層コード
    @RequestMapping(value = "/userLogin", method = RequestMethod.POST)
        public Map ajaxLogin(User userInfo, HttpServletResponse response) {
            Map result = new HashMap<>(4);
            User vo = this.userService.getUserByUserName(userInfo.getUserName());
            if (null != vo && vo.getPassword().equals(userInfo.getPassword())) {
                String tokenStr = JWTUtil.sign(userInfo.getUserName(), userInfo.getPassword());
                userService.addTokenToRedis(userInfo.getUserName(), tokenStr);
                result.put("code", CodeAndMsgEnum.SUCCESS.getcode());
                result.put("msg", "    !");
                response.setHeader("Authorization", tokenStr);
            } else {
                result.put("code", CodeAndMsgEnum.ERROR.getcode());
                result.put("msg", "       !");
            }
            return result;
        }