Refresh Token Rotation: 刷新令牌轮换与重放检测
OAuth2 / JWT 体系里的 refresh token,如果只靠"签名 + 过期时间"校验,是无状态的:服务端不记录任何已发放、已使用的记录,因此没法检测重放、也没法单独撤销某一次登录。要补齐这块安全能力,业界通用做法叫 refresh token rotation(轮换)+ reuse detection(重放检测),思路和 Spring Security 的持久化 remember-me(见 Spring Security Remember Me 实现机制)几乎一致,只是术语不同。 无状态 refresh token 的问题 最简单的实现:refresh token 就是一个长期有效的签名字符串,服务端只验证签名和过期时间,不查库。 优点:无状态,扩展性好。 缺陷:refresh token 一旦泄露,在过期之前可以被无限次使用,服务端发现不了,也撤销不了单独一次登录(除非撤销全部密钥或拉黑名单)。 这跟 Spring Security 默认的 TokenBasedRememberMeServices(哈希令牌)是同一类问题:都是"无状态签名",天然缺少重放检测和单点撤销能力。 Rotation + Reuse Detection 的做法 给每一条 refresh token 的"血统"维护一个 family_id(有的实现里叫 session_id、lineage_id,作用等价于 remember-me 里的 series): family_id current_token issued_at used 用户登录时,颁发 family_id + 第一个 refresh_token,同时记录到数据库。 客户端用 refresh_token 换新的 access_token 时,服务端同时颁发一个新的 refresh_token,旧值作废,数据库里 current_token 更新为新值。 如果收到的 refresh_token 匹配 family_id 但不等于数据库里记录的 current_token——说明这个 token 已经被换过一次了,现在又被用了一次,只有两种可能:客户端重复提交,或者 token 被别人窃取后重放。 一旦判定为重放,直接撤销整条 family_id,强制该登录下线重新认证。 这跟持久化 remember-me 的判定逻辑完全对应: ...