问题:你的 JWT 能"踢人下线"吗
几乎所有 JWT 教程都止步于"生成 token 并通过过滤器校验"。但真实业务里很快就会遇到一个需求:强制某个用户下线。
此时纯无状态方案会暴露它的边界——签发过的 token 在过期前始终有效,服务端没有撤销手段。
无状态鉴权到底牺牲了什么
JWT 的核心优势是把会话状态从服务端搬到客户端,换来的是水平扩展的便利。代价就是丧失了服务端的会话控制权。
判断标准其实很简单:
| 场景 | 适合纯 JWT | 需要会话控制 |
|---|---|---|
| 内部工具、短期接口 | ✅ | |
| 需要强制下线的 C 端 | ✅ | |
| 权限频繁变更 | ✅ | |
| 微服务间内部调用 | ✅ |
补救方案一:缩短有效期 + 刷新令牌
public record TokenPair(String accessToken, String refreshToken) {}
public TokenPair issue(User user) {
String access = Jwts.builder()
.subject(user.getId().toString())
.claim("role", user.getRole())
.issuedAt(new Date())
.expiration(Date.from(Instant.now().plus(15, ChronoUnit.MINUTES)))
.signWith(key)
.compact();
String refresh = randomToken(); // 刷新令牌存库,可撤销
refreshStore.save(user.getId(), refresh, Duration.ofDays(7));
return new TokenPair(access, refresh);
}
访问令牌只活 15 分钟,撤销时只需删除刷新令牌。窗口期小到可接受。
补救方案二:加一层黑名单
如果业务要求"立即生效",就得引入状态。用 Redis 存被撤销的 token 标识,成本很低:
public boolean isRevoked(String jwtId) {
return redis.hasKey("jwt:revoked:" + jwtId);
}
public void revoke(String jwtId, Duration ttl) {
// TTL 设为 token 剩余有效期,过期自动清理
redis.opsForValue().set("jwt:revoked:" + jwtId, "1", ttl);
}
落地建议
- 访问令牌有效期控制在 15–30 分钟
- 刷新令牌入库,支持撤销
- 权限敏感的接口额外做一次实时校验
- 不要在 JWT 里放敏感信息,它是可解码的,不是加密的
小结
没有万能方案。先把业务对"会话控制"的要求想清楚,再决定用纯无状态还是混合方案。很多项目的问题不是选错了技术,而是没意识到选型时牺牲了什么。
