一、前言
最近做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:全是个人理解,请大佬们指导一下👀