Shiro整合JWT实现无状态鉴权机制(Token)

先讲一下大概步骤

  1. JWT工具类,这个在网上找的。
  2. 自定义realm,继承AuthorizingRealm,重写认证和授权两个方法
  3. 自定义filter,继承BasicHttpAuthenticationFilter
  4. 我们使用JWT,所以直接把session禁用。
  5. 重点,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,重写认证和授权两个方法
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);
    }
}
  • 禁用session
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;
    }

你可能感兴趣的:(Shiro)