🔐 深度解析:基于 JWT + Redis 白名单的双令牌高安全认证架构
在现代微服务与分布式系统中,如何平衡安全性(Security)与性能(Performance)一直是身份认证领域的难题。传统的 Session 方案依赖服务端存储,难以扩展;而纯无状态的 JWT 虽然性能好,却面临"无法即时撤销"的安全隐患。
本文将详细介绍一种结合了 RS256 非对称加密 、双令牌机制(Access + Refresh Token) 以及 Redis 白名单 的企业级认证方案。该方案实现了 15 分钟短效访问 + 7 天长效刷新的安全会话管理,并具备即时阻断能力。
1. 核心概念:为什么需要双令牌?
我们采用了两个令牌,各司其职:
-
🔑 Access Token (访问令牌)
-
时效:极短(如 15 分钟)。
-
作用:作为访问受保护 API 资源的"钥匙"。
-
载荷:包含用户 ID、角色、权限等业务信息。
-
验证方式 :无状态验证 。网关或资源服务器只需使用公钥验证签名是否合法,无需查询数据库,性能极高。
-
过期后果:客户端收到 401 错误,需要静默刷新。
-
🔄 Refresh Token (刷新令牌)
-
时效:较长(如 7 天)。
-
作用 :唯一的用途是用来换取新的 Access Token。它不直接用于请求业务数据。
-
验证方式 :有状态验证 。除了验证签名,必须检查 Redis 白名单中是否存在该令牌。
-
机制 :Refresh Token Rotation(令牌轮换)。每次刷新时,旧的 Refresh Token 作废,签发全新的一对令牌。
2. 架构流程详解
下图展示了从登录到令牌刷新的完整生命周期:

2.1 用户登录 (初始化)
- 用户提交凭证:用户输入用户名密码。
- 验证与生成 :
AuthService验证通过后,调用JwtService。 - 签发双令牌:
- 生成
Access Token(RS256 签名)。 - 生成
Refresh Token(RS256 签名)。
- 白名单存储 :将
Refresh Token的唯一标识(如 JTI)或整个 Token 存入 Redis,Key 为user:refresh:{userId},设置 TTL 为 7 天。 - 返回客户端:前端将两个 Token 保存(通常 Access Token 在内存/闭包,Refresh Token 在 HttpOnly Cookie 或安全存储)。
2.2 资源访问 (高频操作)
- API 请求 :前端在 HTTP Header 中携带
Bearer <Access Token>发起请求。 - 令牌验证 :资源服务器/网关解析 JWT,使用 RS256 公钥验签。
- 注意:此时通常不查 Redis,保证 API 响应速度。
- 放行/拒绝:
- 如果有效:执行业务逻辑。
- 如果过期 :返回
HTTP 401 Unauthorized(特定错误码,如TOKEN_EXPIRED)。
2.3 令牌刷新链路 (核心问题解答)
Q:Access Token 过期后会发生什么?
A:前端会自动发起"续命"流程,用户无感知。
当 Access Token (15分钟) 过期,前端捕获到 401 错误后,会触发拦截器逻辑:
- 发起刷新请求 :前端暂停当前业务请求,携带
Refresh Token调用后端/auth/refresh接口。 - 后端验证:
- Step 1 验签:验证 Refresh Token 签名是否合法。
- Step 2 查白名单 :检查 Redis 中该用户的
key是否包含这个 Refresh Token。这是安全的核心!
- 签发新令牌对 (Token Rotation):
- 如果校验通过,后端生成全新 的
Access Token(新15分钟) 和 全新 的Refresh Token(新7天)。
- 更新白名单:
- 删除 Redis 中旧的 Refresh Token。
- 存入新的 Refresh Token。
- 重试请求:前端拿到新令牌后,重发刚才失败的业务请求。
Q:如果 Refresh Token 也快失效了怎么办?
A:自动顺延(Rolling Session)。
在上述第 3 步中,每次刷新都会签发新的 Refresh Token。只要用户在 7 天内活跃过一次,他的会话有效期就会重新计算(再续 7 天)。只有当用户超过 7 天没有打开过应用,Refresh Token 彻底过期且被 Redis 淘汰,此时用户才需要重新登录。
3. 安全方案深度剖析
3.1 为什么是"白名单"而不是"黑名单"?
很多方案使用 JWT 黑名单(注销时把 Token 加入 Redis)。但在你的方案中,使用白名单(只认 Redis 里存在的 Refresh Token)具有碾压级优势:
| 特性 | 白名单模式 (推荐) | 黑名单模式 |
|---|---|---|
| 原理 | 只有在 Redis 里的 Token 才是有效的 | 只有在 Redis 里的 Token 是无效的 |
| 存储大小 | 小。每个用户仅存 1 个活跃 Refresh Token | 大。需要存储所有已注销但未过期的 Token |
| 安全性 | 极高。Redis 数据丢失 = 用户需重新登录 (Fail-Secure) | 中等。Redis 挂了/数据丢失 = 黑客的 Token 复活 (Fail-Open) |
| 设备管理 | 天然支持。踢某人下线只需删他的 Key | 复杂。需要维护复杂的黑名单逻辑 |
3.2 RS256 签名的作用
- 非对称加密:使用私钥(Private Key)在认证服务(Auth Server)进行签名,使用公钥(Public Key)在各个微服务或网关进行验证。
- 优势 :即使某个微服务的代码泄露了,黑客拿到的也只是公钥,他无法伪造 Token,只能验证 Token。这比对称加密(HS256,各方共享同一个密钥)安全得多。
3.3 应对安全事件(即时撤销)
这是本方案最强大的地方。当发生以下场景时:
- 用户修改密码:后端直接删除 Redis 中该用户的 Refresh Token Key。
- 检测到异地登录/可疑活动:后端删除 Redis Key。
- 管理员封号:后端删除 Redis Key。
后果:
- 黑客手里的
Access Token最多还能用几分钟(直到15分钟过期)。 - 当 Access Token 过期,黑客尝试用
Refresh Token刷新时,后端查询 Redis 发现Key 不存在。 - 刷新失败,强制下线。
4. 核心代码逻辑 (伪代码)
java
// JwtService.java
public TokenResponse refreshToken(String incomingRefreshToken) {
// 1. 基础验签 (RS256)
String username = extractUsername(incomingRefreshToken);
if (!isSignatureValid(incomingRefreshToken)) {
throw new AuthException("令牌篡改");
}
// 2. 检查 Redis 白名单 (核心逻辑)
// Key 生成策略: "login:rt:" + username
String storedToken = redisTemplate.opsForValue().get("login:rt:" + username);
// 如果 Redis 里没东西,或者存的东西跟传进来的不一样 -> 拒绝
if (storedToken == null || !storedToken.equals(incomingRefreshToken)) {
throw new AuthException("会话已失效或在其他设备登录");
}
// 3. 生成新的一对令牌
String newAccessToken = generateAccessToken(username);
String newRefreshToken = generateRefreshToken(username);
// 4. 更新 Redis (原子操作)
// 撤销旧的,保存新的,重置 7 天有效期
redisTemplate.opsForValue().set("login:rt:" + username, newRefreshToken, 7, TimeUnit.DAYS);
return new TokenResponse(newAccessToken, newRefreshToken);
}
5. 总结
该架构通过动静分离的策略完美解决了 JWT 的痛点:
- 高性能:99% 的请求(API 访问)只走本地公钥验签,不查库,不查 Redis,速度飞快。
- 高安全:1% 的请求(令牌刷新)走强校验,通过 Redis 白名单控制会话生命周期。
- 可控性:赋予了系统管理员"随时切断用户连接"的能力,不再受制于 JWT 的有效期。
这不仅是一个登录功能,更是一套符合金融级安全标准的企业级会话管理方案。