【安全实战】无状态JWT如何瞬间踢人?基于双Token与Redis黑名单的工程解法
在云原生微服务中,JWT 凭借其“无状态(Stateless)”的特性,让 API 网关在校验用户身份时无需再查询数据库,极大地提升了系统的横向扩展能力。 但无状态是一把双刃剑:一旦签发,只要 Token 没过有效期,就算管理员在数据库里把用户拉黑了,黑客依然可以拿着旧 Token 畅通无阻地访问系统。
标准的云原生工程解法是:“极短生命周期的 Access Token + 缓存黑名单兜底拦截”。
1. 实施双 Token 架构(分离鉴权与续期)
绝对不要签发有效期长达一周的单 JWT。必须将 Token 拆分为两部分:
-
Access Token(访问凭证):有效期极短(如 15 分钟),每次请求必须携带。 -
Refresh Token(续期凭证):有效期长(如 7 天),仅用于换取新的 Access Token。
这样,即便黑客窃取了 Access Token,威胁窗口也被严格限制在了 15 分钟内。
2. 利用 JTI 构建 Redis 毫秒级黑名单
如果连 15 分钟的窗口期都不能容忍,必须实现“秒级踢人”,就需要在 JWT 的载荷(Payload)中植入 jti(JWT ID,该 Token 的全局唯一标识)。 当管理员触发“踢出用户”操作时,网关拦截器立刻将该 Token 的 jti 写入 Redis 黑名单池。
// 强制使某个 JWT 失效(踢人下线操作)
public void revokeToken(String jwtId, long remainingExpireTimeInSeconds) {
// 💡 核心:Key 为 JTI,TTL 必须严格等于该 JWT 剩余的真实有效期
// 这样当 JWT 自身物理过期后,Redis 中的黑名单记录也会自动消失,不浪费一丝内存
String blacklistKey = "jwt:blacklist:" + jwtId;
redisTemplate.opsForValue().set(blacklistKey, "revoked", remainingExpireTimeInSeconds, TimeUnit.SECONDS);
}
3. API 网关层的 O(1) 极速拦截
在 Spring Cloud Gateway 或 Nginx Lua 脚本中,解析出 JWT 后,在验证签名前多加一步极速的 Redis 查询:
// API 网关全局过滤器中的黑名单校验逻辑
String jti = parseJwtToGetJti(token);
Boolean isRevoked = redisTemplate.hasKey("jwt:blacklist:" + jti);
if (Boolean.TRUE.equals(isRevoked)) {
// 拦截器直接熔断,抛出 HTTP 401 Unauthorized
return Mono.error(new UnauthorizedException("该凭证已被服务端强制吊销"));
}
// 校验通过,放行请求...
总结: 不要为了实现“服务端强制注销”,就把庞大的用户 Session 重新塞回 Redis 中,那将彻底破坏 JWT 的无状态初衷。通过“短效 Token 控制爆炸半径 + Redis JTI 黑名单实施点对点狙击”,我们在确保网关鉴权超高吞吐量的同时,补齐了云原生架构下极度缺乏的安全管控能力。
- 点赞
- 收藏
- 关注作者
评论(0)