业务实战:JWT Token 过期、被盗怎么处理?基于 Spring Boot 的双 Token 刷新方案解析
最近在重构公司的后端鉴权模块,发现很多新人对 JWT(JSON Web Token)的理解只停留在“调库生成一段字符串”的阶段。一旦上线到生产环境,各种业务客诉就来了:用户填了半天表单,点提交时 Token 过期直接跳回登录页;或者安全部门来扫描,问你 Token 被截获了怎么强制让用户下线?
纯“无状态”的 JWT 在真实业务中其实是个伪命题。今天借着 GitHub 上一个非常经典的优质开源项目——spring-boot-refresh-token-jwt by bezkoder,来聊聊我们在生产环境中到底该怎么处理 JWT 的这几个老生常谈的痛点。
1. jwt token过期解决方法:双 Token 与数据库状态化
痛点: Token 有效期设长了不安全,设短了用户体验极差。
很多人第一反应是“自动续期”,每次请求都在 Header 里塞一个新的 Token 返回给前端。这其实非常浪费带宽和后端计算资源。
主流且优雅的做法是引入 Access Token (AT) + Refresh Token (RT) 的双 Token 机制。
我们来看看 bezkoder 这个开源项目是怎么巧妙实现的。它并没有让 Refresh Token 也是个无状态的 JWT,而是把 Refresh Token 存在了数据库里。
核心逻辑拆解:
- 登录时:后端生成一个寿命短的 Access Token(比如 15 分钟),同时生成一个随机 UUID 作为 Refresh Token(寿命设为 7 天),并将这个 RT 保存到数据库表
refresh_tokens中,关联当前 UserID。 - 正常请求:前端带着 AT 请求接口,没过期就正常放行。
- AT 过期:前端拿到
401状态码,立刻静默拿着之前的 RT 去请求/api/auth/refreshtoken接口。
看一眼该项目库里的核心刷新逻辑:
@PostMapping("/refreshtoken")
public ResponseEntity<?> refreshtoken(@Valid @RequestBody TokenRefreshRequest request) {
String requestRefreshToken = request.getRefreshToken();
// 从数据库中查找这个 Refresh Token
return refreshTokenService.findByToken(requestRefreshToken)
.map(refreshTokenService::verifyExpiration) // 校验数据库里的 RT 是否过期
.map(RefreshToken::getUser)
.map(user -> {
// 生成新的 Access Token
String token = jwtUtils.generateTokenFromUsername(user.getUsername());
return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
})
.orElseThrow(() -> new TokenRefreshException(requestRefreshToken, "Refresh token is not in database!"));
}
为什么要把 RT 存数据库? 因为这不仅解决了无感刷新,还顺带解决了一个极其棘手的安全问题(详见第 4 点)。
2. jwt token为空怎么处理?
痛点: 难道要在每一个 Controller 接口里都写一句 if(token == null) 吗?
这其实是网关或拦截器该干的事。在基于 Spring Security 的项目中(如上文的开源项目),通常会自定义一个 AuthTokenFilter。
对于那些非公开的接口,如果 Token 为空,不应该把异常抛到业务层,而应该在 Filter 层面直接打回。
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
try {
String jwt = parseJwt(request);
// token 为空或校验不通过,根本走不到业务逻辑
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
// 组装 Authentication 放入 SecurityContext
// ...
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
如果是普通的 Spring Web 项目,写一个 HandlerInterceptor,在 preHandle 里判断请求头 Authorization。为空直接 response.setStatus(401),返回统一的 JSON 错误码即可。
3. jwt token获取用户信息:拒绝重复解析
痛点: 很多开发喜欢在 Service 层需要用到 userId 的时候,再去解析一遍 Token,或者层层传参。这既耗费 CPU,又让代码耦合严重。
正解是配合 ThreadLocal 实现上下文透传。
一旦我们在前面的拦截器(Filter/Interceptor)中校验 Token 合法,就顺手把 Payload 里的 userId 解析出来,塞进当前线程的上下文中。
// 1. 定义一个简单的 Context
public class UserContext {
private static final ThreadLocal<String> USER_ID = new ThreadLocal<>();
public static void set(String userId) { USER_ID.set(userId); }
public static String get() { return USER_ID.get(); }
public static void remove() { USER_ID.remove(); }
}
// 2. 在 Controller/Service 中直接拿,再也不用碰 Token
public void createOrder() {
String currentUserId = UserContext.get();
// 业务逻辑...
}
注意:由于 Tomcat 的线程池复用机制,切记在请求结束(afterCompletion)时调用 UserContext.remove(),否则会导致严重的内存泄漏和用户串号问题。
4. jwt token被盗怎么解决?
痛点: 纯无状态的 JWT 最大的死穴——一旦颁发,后端无法主动废弃。如果黑客通过 XSS 窃取了用户的 Token,怎么强制让他下线?
这时候,前文提到的 bezkoder 开源项目中**“数据库存储 Refresh Token”**的威力就体现出来了。
- 针对 Access Token: 既然我们把 AT 的寿命压得很短(15分钟),黑客拿到了也只能嚣张一小会儿。
- 针对 Refresh Token: 这是一个长效 Token。如果用户发现账号异地登录,或者管理员需要封禁某个用户,直接在数据库的
refresh_tokens表中,DELETE掉该用户关联的记录即可。
黑客手里的 AT 过期后,必然会带着偷来的 RT 去换新 Token。此时后端去数据库一查:RT 不存在,直接抛出 TokenRefreshException,强制黑客重新走账密登录。
进阶防盗手段:绑定指纹
在生成 Token 时,提取用户当前请求的 User-Agent 或 IP 地址 取 MD5,一起塞进 JWT 的 Payload 里。校验 Token 时,如果发现当前请求的 IP 变了,直接判定 Token 无效。这招防 XSS 盗用非常有效。
- 重构后的 SpringBoot 双 Token 核心源码(全中文注释版)。
- Vue3 配合无感刷新的完整前端登录页与 Axios 拦截器配置文件。
- 配套的 MySQL 基础建表脚本与 Postman 接口调试集合。
👇👇 全栈双 Token 落地包获取方式 👇👇
链接:https://pan.quark.cn/s/1f0215d12525
提取码:gd7c
其实,把 JWT 用在传统的 Web Session 鉴权场景里,为了弥补其无状态带来的缺陷,我们通常都会把它“重新状态化”(引入数据库/Redis)。
如果你目前正在做这方面的需求,强烈建议 Clone 一下 https://github.com/bezkoder/spring-boot-refresh-token-jwt 这个仓库跑一遍。看看别人是怎么用 Spring Data JPA 优雅地管理 Refresh Token 生命周期的,这比你自己从零开始造轮子要稳健得多。
大家在公司项目里,是怎么处理多设备登录被踢下线这类 JWT 场景的?欢迎在评论区探讨。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)