技术点拆解:登录与身份验证(JWT + ThreadLocal)


1. 核心实现原理

技术要点
JWT结构
• Header(算法类型,如HS256)、Payload(用户信息+过期时间)、Signature(签名=HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret))。
示例eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
ThreadLocal作用
• 在拦截器中解析Token,将用户信息存入ThreadLocal,后续业务层直接通过ThreadLocal.get()获取用户上下文,避免频繁传参。


2. 高频面试问题与回答示例

Q1:为什么选择JWT而不是传统的Session-Cookie方案?

回答示例

“项目是分布式架构,Session需要服务端存储用户状态,如果采用Session,在集群环境下需要同步Session数据(比如用Redis),增加了复杂度。而JWT是无状态的,服务端只需验证签名,天然适合分布式系统。此外,JWT的Payload可以携带业务信息(如用户角色),减少数据库查询。”

追问点
JWT的缺点
• Token一旦签发无法主动失效(除非等到过期),解决方案:用Redis维护一个黑名单,用户注销时将未过期的Token加入黑名单,拦截器校验时额外检查黑名单。
安全性问题
• 敏感信息不应放在Payload中(因为Payload是Base64编码,可解码),需加密存储或仅放非敏感数据。


Q2:如何防止JWT被篡改?Signature是如何生成的?

回答示例

“JWT的Signature部分使用服务端的密钥(secret)对Header和Payload进行HMAC-SHA256签名。如果攻击者篡改了Payload,由于他不知道密钥,无法生成正确的Signature,服务端校验时会发现签名不一致,拒绝请求。”

技术扩展
密钥管理
• 密钥不能硬编码在代码中,应通过环境变量或配置中心动态获取。
• 定期轮换密钥(如每月更换),旧密钥需保留一段时间以兼容未过期的Token。


Q3:为什么用ThreadLocal?会不会导致内存泄漏?

回答示例

“ThreadLocal的作用是将用户信息绑定到当前线程,避免在方法参数中层层传递。但Tomcat使用线程池,线程会被复用,如果不在拦截器处理完成后及时清理ThreadLocal,之前用户的数据可能残留在线程中,导致后续请求拿到错误信息。因此,我**在拦截器的afterCompletion()方法中调用ThreadLocal.remove()**,确保线程归还前清除数据。”

深入问题
ThreadLocal底层原理
• 每个Thread内部维护一个ThreadLocalMap,Key是ThreadLocal对象,Value是存储的值。
内存泄漏原因:ThreadLocal对象作为Key是弱引用,但Value是强引用。若ThreadLocal对象被回收,但Value仍存在,需手动remove()。


Q4:Token过期后如何实现无感刷新?

回答示例

“在拦截器中判断Token即将过期(比如剩余时间小于30分钟),生成新Token并放入响应头(如Refresh-Token),前端检测到新Token后替换旧Token。或者采用双Token方案:AccessToken(短有效期)和RefreshToken(长有效期),通过RefreshToken重新获取AccessToken。”

注意事项
• RefreshToken需持久化存储(如数据库),并设置较严格的过期时间和校验逻辑。


Q5:如何解决集群环境下拦截器的ThreadLocal数据不同步?

回答示例

“ThreadLocal是线程隔离的,天然不支持跨服务共享数据。但项目中ThreadLocal仅用于单个请求链路内的上下文传递,不涉及跨服务。如果需要跨服务传递用户信息(如微服务架构),会将用户ID放入请求头或RPC上下文(如Dubbo的Attachment),下游服务重新解析并存入自己的ThreadLocal。”


3. 项目中的优化与反思

优化点
• 使用Hash存储用户信息节省内存,但需注意Redis的Hash编码(ziplist或hashtable),小数据量时ziplist更省内存。
反思点
• 初始版本未考虑Token续期,导致用户体验较差,后期通过双Token方案优化。


4. 模拟追问链

  1. :JWT的Payload中存储了哪些信息?
    :用户ID、角色、过期时间(exp)、签发时间(iat)等,不包含密码等敏感信息。
  2. :拦截器的执行顺序是怎样的?
    preHandle()按顺序执行,postHandle()逆序执行,afterCompletion()逆序执行。
  3. :如果用户篡改Payload中的用户ID,系统如何防范?
    :Signature校验会失败,因为篡改后的Payload无法生成正确的签名。

总结

核心知识点:JWT无状态原理、ThreadLocal内存泄漏防范、Token安全设计。
回答技巧
结合项目:强调项目中具体做了什么(如拦截器清理ThreadLocal)。
对比方案:说明为什么选JWT而非Session,体现技术选型能力。
主动延伸:提到后续优化(如双Token方案),展示迭代思维。

最后一句话
“在实现登录模块时,我重点关注了安全性和扩展性,通过JWT+Redis黑名单解决了无状态问题,通过ThreadLocal+拦截器优化了代码结构,最终系统支持了每日10万+的登录请求,且未出现内存泄漏或Token篡改问题。”

祝你在AI面试中游刃有余! 🚀