JWT、拦截器与 ThreadLocal 用户上下文

接口需要知道“当前请求是谁发起的”,通常要先验证身份,再把必要的用户标识传给业务逻辑。JWT、拦截器和 ThreadLocal 分别负责不同事情:令牌携带可验证的声明,拦截器检查请求,ThreadLocal 在同一请求线程内传递上下文。

1. JWT 做什么,不做什么

JWT 通常由头部、载荷和签名组成。签名用于检测令牌是否被篡改;Base64URL 编码不是加密,因此载荷不应放密码、密钥或其他机密信息。服务端需要验证签名、算法、签发者、受众和有效时间等约束,而不是只解析出用户 ID 就信任它。

JWT 常被称作无状态认证,因为服务端可以验证令牌而不必每次查询会话表。但登出、封禁、权限即时变更和密钥轮换仍需要策略;短有效期、撤销列表或服务端状态各有取舍。

2. 请求认证流程

1
2
3
4
5
6
7
客户端携带 Authorization: Bearer <token>
→ Filter / 拦截器提取令牌
→ 验证签名与有效期
→ 读取可信的用户标识
→ 保存到当前请求上下文
→ 控制器与业务层执行授权检查
→ 请求结束后清理上下文

身份认证回答“是谁”,授权回答“能做什么”。控制器不能只因令牌有效就允许用户访问任意资源;涉及用户数据时,还要检查当前用户是否有权操作对应对象。

3. Filter 与 HandlerInterceptor

Filter 在 Servlet 层工作,能覆盖进入 Servlet 的请求;Spring MVC 拦截器在匹配到处理器前后执行,可以依据路由规则排除静态资源或公开接口。选择哪种机制取决于任务:跨应用通用的请求处理常放 Filter,依赖 Spring MVC 处理器映射的逻辑可放拦截器。安全校验也可以使用 Spring Security 等完整安全框架。

4. ThreadLocal 的正确使用

在同步请求中,拦截器可将当前用户摘要放入 ThreadLocal,业务层通过统一上下文工具读取。Servlet 线程池会复用线程,因此清理必须放在确定执行的结束路径,例如拦截器 afterCompletion 或 Filter 的 finally:

1
2
3
4
5
try {
chain.doFilter(request, response);
} finally {
UserContext.remove();
}

如果只在成功路径清除,异常请求就可能把旧用户信息留在线程上。异步线程、线程池任务和响应式链路也不会自动继承普通 ThreadLocal,上下文需要显式传递或使用框架提供的机制。

5. 日志和数据保护

不要在日志中输出完整 JWT。日志可记录请求 ID、用户内部 ID 和验证结果,但应按隐私与安全要求控制访问和保留期限。令牌通过 HTTPS 传输,避免放在 URL、公开链接或截图中。

6. 小结

认证、授权和请求上下文要分开设计。JWT 的签名不是加密,ThreadLocal 不是跨线程存储;请求结束后的清理和资源级授权同样重要。