一、前言
最近做Jwt token续签的时候,在很多博客和下载的代码中,都是在JWTFilter中进行token的刷新,于是就按照了网上的代码进行尝试,代码如下:
1. 代码
- 在JWTFilter中的isAccessAllowed方法
 目的:就是想通过executeLogin内部的方法,出现了token过期,然后返回TokenExpiredException的异常,就进行refreshToken。
 @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //判断请求的请求头是否带上 "Token"
        if (isLoginAttempt(request, response)){
        	// 省略一些其他操作,主要看以下核心内容
			try {
                 //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
                 executeLogin(request, response);
                 return true;
             }catch (Exception e){
                 /*
                  * 注意这里捕获的异常其实是在Realm抛出的,但是由于executeLogin()方法抛出的异常是从login()来的,
                  * login抛出的异常类型是AuthenticationException,所以要去获取它的子类异常才能获取到我们在Realm抛出的异常类型。
                  * */
                 Throwable cause = e.getCause();
                 if (cause!=null&&cause instanceof TokenExpiredException){
                     //AccessToken过期,尝试去刷新token
                     String result = refreshToken(request, response);
                     if (result.equals("success")) {
                         return true;
                     }
                 }
             }
		}
    	return false;
    }        
- 在JWTFilter中的refreshToken方法
 目的:刷新token,进行续签。
 PS:这里主要说问题,不做详细说明
	@Autowired
    @Lazy
    private RedisUtil redisUtil;
    
	private Boolean refreshToken(ServletRequest request,ServletResponse response) {
        HttpServletRequest req= (HttpServletRequest) request;
        // 获取传递过来的accessToken
        // 从请求头header中获取字段名为ACCESS_TOKEN的值(也就是我们说的token)
        String token = req.getHeader(CommonConstant.ACCESS_TOKEN);
        // redis中的token 定义前缀+token 为缓存中的key,得到对应的value(cacheToken)
        Object cacheToken = redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token);
        // 获取token里面的用户名
        String userName = JwtUtil.getUsername(token);
        // 判断refreshToken是否过期了,如果过期了,redis的key将不存在
        if (CommonUtils.isNotEmpty(cacheToken)){
			...
		}
2. 设置
使用的依赖:
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.2.0</version>
		</dependency>
为了测试这部分正常使用,设置了token的签证过期时间为1分钟,redis中存储token的过期时间也为1分钟。
PS:看网上都是这样设置的。
二、问题
1. 问题一:e.getCause()获取不到TokenExpiredException
登录获取token后,过了1分钟,在访问接口,断点打在了Throwable cause = e.getCause();,查看捕获的异常为org.apache.shiro.authc.UnknownAccountException: Realm,如下:
 
 翻译过来就是:找不到提交的AuthenticationToken的帐户数据。
 想到token过期就1分钟,好像也对,redis存的过期,token也过期,但是为啥没有获取到TokenExpiredException这个异常?
 TokenExpiredException:token过期而抛出的异常。
2. 问题二:refreshToken方法走不下去
- 为什么redisUtil为null
- 即使redisUtil不为null,但cacheToken也为null

三、查看源码
- 从executeLogin往下看源码(问题二)
 这个的代码,主要是执行了Subject接口中的login,详细的可以看下==【shiro】subject.login(token)源码== 这篇文章,这里给出流程图:
  基于以上文章的内容,快步查看源码。 基于以上文章的内容,快步查看源码。
根据 问题 中的截图,对着下面subject.login(token)的流程查看报错信息在哪输出,最后发现在ModularRealmAuthenticator类中。
 
 该异常主要是因为info返回值为null,具体看下AuthenticationInfo info = realm.getAuthenticationInfo(token);
 
 这个类大家应该都很熟悉了,看到doGetAuthenticationInfo就是我们在realm中重写的身份验证方法。
 这个类中要得到info返回值为null,那就得看getCachedAuthenticationInfo(token)方法。
 
 在该方法中,主要判断cache和token是否为null,如果为null那info只能返回null。
 PS:这个地方还是有点迷,debug进来会发现,redis即使没有过期,这里的cache还是为null,而且这地方为null,整个流程其实还是正常进行。(这里点以后遇到在继续研究)
四、总结
redis和token同时过期,以上代码就没法进行token续签了。
- 问题一
 主要原因是token过期,所以查不到信息,导致返回org.apache.shiro.authc.UnknownAccountException: Realm
 解决一:redis中token过期时间设为20分钟,token签证的过期时间设为1分钟,将两个时间错开可以执行,再去实验,就可以得到TokenExpiredException的异常了。(具体没再往下深究)
- 问题二
 2.1 原因:拦截器在bean初始化前执行的,这时候redisUtil是null,需要通过SpringUtils进行反射获取
 2.2 原因:token过期了(问题一中同样的问题)
 解决二:将两个时间错开
PS:全是个人理解,请大佬们指导一下👀



















