场景
微服务都是无状态的,所以基本都是使用Token来做用户身份校验的,一般开发都会在一个请求的最初将用户Header中的token放入ThreadLocal中,key为当前线程,value为token相关信息;
拦截器入口放入ThreadLocal中
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("Authorization");
//带有匿名访问注解的方法不需要校验
if (method.isAnnotationPresent(Anonymous.class) && StringUtils.isEmpty(token)) {
return true;
}
//校验token
Claims claims = JwtHelper.verifyJwt(token);
setUser(claims);
return true;
}
private void setUser(Claims claims) {
Long userId = claims.get("userId", Long.class);
String name = claims.get("name", String.class);
JWTUserDto jwtUserDto = new JWTUserDto(userId, name);
AuthManager.setUser(jwtUserDto);
}
}
//授权管理器
public class AuthManager {
private static ThreadLocal<JWTUserDto> threadLocal = new ThreadLocal<>();
public static JWTUserDto currentUser() {
return threadLocal.get();
}
static void setUser(JWTUserDto authUser) {
threadLocal.set(authUser);
}
}
业务方法中获取当前Request的用户
JWTUserDto jwtUserDto = AuthManager.currentUser();
问题
场景:用户查看文章下面会有文章推荐,未登录的用户一种规则,登录的用户推荐另一种规则。
JWTUserDto jwtUserDto = AuthManager.currentUser();
if (jwtUserDto != null) {
return "一种规则";
}
return "另一种规则";
偶然间发现很诡异的随机出现bug,未登录的用户有时候竟然得到了登录用户的内容。返回结果不固定随机变化
分析
我们都知道ThreadLocal的key是当前线程。数据错乱可能是线程key的问题。就用如下代码测试
//在拦截器中或者业务方法中增加测试代码
System.err.println(Thread.currentThread().getId());
多次请求测试,发现打印结果线程id是从57-89这个范围,超过的继续从57开始。
这就表明了请求是使用的线程池,会复用thread,而threadLocal的key就是用的它,所以会出现偶尔会发生的bug
解决
既然知道了老的ThreadLocal使用后没有删除,导致线程复用的时候(当前线程又不需要token没有设置)就得到了老的数据,我们就在拦截器的每次请求之后删除掉ThreadLocal的数据。(tips:不删除也可能会导致ThreadLocal的内存泄漏问题)
public class AuthManager {
//增加方法
public static void remove() {
threadLocal.remove();
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
AuthManager.remove();
}