1.背景:
随着业务的增长,线上系统qps越来越高,起初市场同事和客户偶尔会反馈用户看到的信息出现的不是自己的信息,但是重新刷新之后就好了,当时我们就对这个问题进行分析,但是都是无疾而终,没有找到问题所在,随着用户增长的基数越来越大,出现这个问题的现象也就更加频繁,所以我们团队对于这个线上事故就非常重视,开始对于整个请求链路分析,我们用户端使用的是一款我们自研的客户端,当时我们分析出来可能存在的几个原因:
- 同一个客户端,不同用户登录,本地缓存的token混乱。
- 客户端因为某种特定场景下拿到了别人的token,或者是登录接口直接颁发的token就是错的
- 接口请求token认证过程中,由于线程的原因,导致线程中用户信息错乱(分析过Spring security oauth2的源码,请求接口是会验证token的有效性——通过访问认证服务器去识别token的有效性,并采用的ThreadLocal模式存储用户身份信息),这边错乱的信息主要是因为,接口请求结束的时候ThreadLocal信息没有清理导致的(tomcat采用线程池)。
2. 验证阶段
首先我们上线了一个紧急修复措施,写了一个统一拦截器,拦截接口请求,验证token中的身份信息(jwt的token认证方式,可以通过payload解析出来用户身份-Base64解码)和Spring security oauth2 认证后获取到身份信息比较,对比成功的放行,失败的拦截,同时打印相关日志。具体代码如下:
@Slf4j
@Component
public class RequestInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 框架集成方法,可以自行源码,其实就是threadLocal 方式存储用户身份信息
String clientId = (String)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (checkToken(request, clientId)) {
return super.preHandle(request, response, handler);
}
Map<String, Object> map = Maps.newHashMap();
map.put("code", 40412);
map.put("message", "请退出,重新登录!");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(map));
return false;
}
private boolean checkToken(HttpServletRequest request,