2. Token生命周期管理:Access Token与Refresh Token的生成、存储、刷新与撤销机制
在OAuth 2.0鉴权流程中,Token的生命周期管理是保障系统安全与性能的核心环节。Access Token作为短期凭证直接访问资源,Refresh Token则用于无感续期。本章深入剖析双Token的生成算法、存储策略、刷新流程与撤销机制,并结合Redis热点风暴场景给出优化方案。
2.1 双Token模型与职责划分
Access Token与Refresh Token在生命周期中承担不同职责,其核心差异如下表所示:
| 属性 | Access Token | Refresh Token |
|---|---|---|
| 用途 | 直接访问受保护资源 | 获取新的Access Token |
| 有效期 | 短(通常15分钟~2小时) | 长(通常7天~30天) |
| 存储位置 | 客户端内存/本地存储 | 客户端安全存储(HttpOnly Cookie/Keychain) |
| 传输频率 | 每次API请求 | 仅在Access Token过期时 |
| 撤销影响 | 立即失效,需重新获取 | 撤销后需用户重新登录 |
| Redis热点风险 | 高(高频读取验证) | 低(低频刷新操作) |
设计原则:Access Token应尽量短命以减少泄露风险,Refresh Token应具备轮换机制(Rotation),每次刷新后生成新的Refresh Token并废弃旧Token,防止重放攻击。
2.2 Token生成机制
2.2.1 生成算法选型
Token生成方式直接影响验证性能与安全性。主流方案对比:
| 方案 | 生成方式 | 验证方式 | Redis依赖 | 推荐场景 |
|---|---|---|---|---|
| JWT(自包含) | 签名+Payload | 本地验签,无需查库 | 低(仅黑名单) | 分布式系统、无状态服务 |
| Opaque Token(不透明) | 随机字符串 | 需查询Redis/DB | 高(每次验证) | 传统单体、需即时撤销 |
| 混合模式 | JWT + 短缓存 | 本地验签 + 缓存兜底 | 中(缓存失效时) | 高并发、需平衡性能与撤销 |
2.2.2 JWT生成示例
// 生成Access Token(JWT格式)
String accessToken = Jwts.builder()
.setSubject(userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 900_000)) // 15分钟
.claim("scope", "read write")
.claim("client_id", clientId)
.signWith(SignatureAlgorithm.RS256, privateKey)
.compact();
// 生成Refresh Token(Opaque格式,随机128位)
String refreshToken = UUID.randomUUID().toString()
+ "-" + SecureRandom.getInstanceStrong().generateSeed(16);
// 存储到Redis,设置7天过期
redisTemplate.opsForValue().set(
"refresh:" + refreshToken,
userId + ":" + clientId,
7, TimeUnit.DAYS
);
避坑提示:JWT的Payload中不要存放敏感信息(如密码、完整手机号),因为JWT仅签名不加密。如需加密,应使用JWE(JSON Web Encryption)标准。
2.3 Token存储策略与Redis热点分析
Token存储位置直接影响Redis的读写压力。下图展示了双Token在Redis中的存储架构与热点形成路径:
2.4 Token刷新流程
Refresh Token刷新流程是OAuth 2.0的核心交互,需兼顾安全性与性能。标准刷新流程包含以下步骤:
- 检测过期:客户端收到401响应或本地判断Access Token过期。
- 发起刷新:客户端携带Refresh Token向授权服务器发送刷新请求。
- 验证Refresh Token:授权服务器查询Redis验证Refresh Token有效性、是否被撤销。
- 轮换Token:生成新的Access Token和新的Refresh Token(轮换模式)。
- 废弃旧Token:将旧Refresh Token加入黑名单或直接删除。
- 返回新Token:客户端更新本地存储,继续使用新Access Token访问资源。
性能优化点:在刷新流程中,授权服务器可同时返回Access Token的过期时间戳,客户端据此提前预刷新(Pre-refresh),避免在请求高峰期集中刷新导致Redis热点。
2.5 Token撤销机制
Token撤销是安全生命周期的重要环节。撤销策略需根据Token类型差异化设计:
| 撤销场景 | Access Token处理 | Refresh Token处理 | Redis影响 |
|---|---|---|---|
| 用户主动登出 | 加入黑名单(TTL=剩余有效期) | 直接删除Redis记录 | 低(单次操作) |
| 密码修改 | 批量加入黑名单(用户维度) | 批量删除用户所有Refresh Token | 中(需扫描) |
| Token泄露 | 立即加入黑名单 | 立即删除并强制用户重新登录 | 低(紧急操作) |
| 管理员强制下线 | 通过用户ID批量撤销 | 通过用户ID批量撤销 | 高(需遍历) |
2.5.1 黑名单实现方案
// 方案一:Redis Set黑名单(适用于JWT)
// 将已撤销的JWT的jti(JWT ID)加入黑名单
String jti = parseJwtClaims(token).getId();
redisTemplate.opsForValue().set(
"blacklist:jwt:" + jti,
"revoked",
remainingTtl, TimeUnit.SECONDS
);
// 方案二:Bloom Filter + Redis兜底(高性能)
// 先通过Bloom Filter快速判断是否可能被撤销
if (bloomFilter.mightContain(jti)) {
// 再查Redis确认
if (redisTemplate.hasKey("blacklist:jwt:" + jti)) {
throw new TokenRevokedException("Token已被撤销");
}
}
// Bloom Filter未命中则直接放行,减少99%的Redis查询
注意:Bloom Filter存在误判率(False Positive),即可能将未撤销的Token误判为已撤销。需根据业务容忍度调整误判率参数(通常设为1%以下),并在Redis中做二次确认。
2.6 生命周期优化策略总结
针对Redis热点风暴场景,Token生命周期管理需从以下维度优化:
- JWT优先:Access Token采用JWT格式,资源服务器本地验签,减少Redis读取频率。
- 本地缓存:对JWT的公钥缓存到本地内存,避免每次验签都请求授权服务器。
- 分层存储:Access Token使用本地缓存+Redis二级缓存,Refresh Token仅存Redis。
- 预刷新机制:客户端在Access Token过期前主动刷新,打散刷新请求时间分布。
- Bloom Filter加速:撤销检查使用Bloom Filter过滤大部分正常Token,减少Redis查询。
- 批量撤销异步化:管理员批量撤销操作使用消息队列异步处理,避免阻塞Redis。
最佳实践:在Redis热点风暴高发场景下,推荐采用“JWT + 本地缓存 + Bloom Filter黑名单”的组合方案,可将Redis的Token验证请求量降低90%以上,同时保证撤销的实时性。