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中的存储架构与热点形成路径:

Token存储架构与Redis热点路径 客户端 存储Access Token 授权服务器 生成/验证Token 资源服务器 验证Access Token Redis集群 Access Token缓存(高频读) Refresh Token存储(低频读写) Token黑名单(撤销记录) ⚠ 热点风暴区域 大量并发验证请求击穿缓存 → Redis过载 优化方向:JWT本地验签减少Redis读取 + 本地缓存 + 分层存储

2.4 Token刷新流程

Refresh Token刷新流程是OAuth 2.0的核心交互,需兼顾安全性与性能。标准刷新流程包含以下步骤:

  1. 检测过期:客户端收到401响应或本地判断Access Token过期。
  2. 发起刷新:客户端携带Refresh Token向授权服务器发送刷新请求。
  3. 验证Refresh Token:授权服务器查询Redis验证Refresh Token有效性、是否被撤销。
  4. 轮换Token:生成新的Access Token和新的Refresh Token(轮换模式)。
  5. 废弃旧Token:将旧Refresh Token加入黑名单或直接删除。
  6. 返回新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%以上,同时保证撤销的实时性。