Skip to content

JWT是无状态的,服务端如何主动让一个已签发的JWT失效

JWT(JSON Web Token)的核心优势之一是其“无状态”特性,即服务端无需保存任何关于令牌的状态信息,仅通过校验令牌的签名即可确认其有效性。然而,正是这个特性导致服务端默认无法主动让一个已经签发且未过期的令牌失效。 尽管这与JWT的初衷相悖,但在许多实际应用场景中(如用户登出、修改密码、管理员强制用户下线),主动让令牌失效是必要的功能。

为了解决这个问题,发展出了多种借助“外力”(通常是引入一个轻量级的状态存储)来间接实现JWT失效的方案。以下是几种主流的方法及其实现思路:

1. 维护Token黑名单 (Blacklist)

这是最常用也是最直接的解决方案。其核心思想是建立一个已失效但尚未过期的Token列表,每次请求时都去查询这个列表。

实现思路: * 存储选择:使用高性能的内存数据库,如Redis,来存储黑名单。 * 注销操作:当用户主动登出或需要强制其下线时,将该用户的当前JWT加入到Redis黑名单中。 * 设置过期时间:为了防止黑名单无限膨胀,存入黑名单的JWT应该设置一个过期时间,这个时间等于该JWT剩余的有效时间。 例如,一个Token总有效期是2小时,在第30分钟时被注销,那么它在黑名单中的存活时间就应该是90分钟。这样可以确保Redis能自动清理掉那些已经自然过期的Token条目。 * 校验流程:在服务端的认证中间件中,除了进行常规的签名和有效期校验外,增加一步操作:查询当前请求的Token是否存在于黑名单中。如果存在,则立即拒绝该请求。 * 优化:为了节省存储空间,可以不存储完整的Token字符串,而是存储其唯一标识jti (JWT ID) claim。

优缺点: * 优点:可以精确地让单个Token失效,控制粒度最细。逻辑清晰,易于实现。 * 缺点:打破了JWT的完全无状态性,每次请求都需要额外查询一次Redis,增加了网络开销和系统延迟。

2. 在JWT中增加版本号或时间戳

这种方法通过为用户关联一个版本标识来批量废除旧的Token,非常适用于修改密码后让所有旧Token失效的场景。

实现思路: * 数据库设计:在用户数据表中增加一个字段,例如 token_version (整数) 或 password_changed_at (时间戳)。 * 签发Token:当为用户签发新的JWT时,将当前用户的 token_versionpassword_changed_at 作为Payload的一个claim加入到JWT中。 * 失效操作:当用户修改密码或执行“所有设备登出”操作时,只需更新数据库中对应用户的 token_version(例如,将其加1)或 password_changed_at 为当前时间。 * 校验流程:在校验Token时,除了基础校验,还需要从数据库中读出该用户的当前版本/时间戳,并与Token中携带的版本/时间戳进行比对。如果Token中的版本小于数据库中的版本,或者Token的签发时间早于密码修改时间,则判定Token无效。

优缺点: * 优点:无需维护庞大的黑名单,存储开销小。一次数据库更新即可让该用户的所有旧Token全部失效。 * 缺点:同样需要每次请求都查询数据库,引入了状态依赖。无法做到只让单个设备上的Token失效(会影响所有持有旧版本Token的设备)。

3. 为每个用户生成唯一的加密密钥 (Secret Key)

此方案通过更改用于签名JWT的密钥来使Token失效。

实现思路: * 数据库设计:在用户数据表中增加一个字段,如 token_secret,为每个用户存储一个唯一的、随机生成的字符串。 * 签发Token:为用户签发JWT时,使用其专属的 token_secret 进行签名。 * 失效操作:当需要让某个用户的所有Token失效时,只需重新生成一个新的随机字符串,并更新该用户在数据库中的 token_secret 字段。 * 校验流程:校验Token时,先从Token的Payload中解析出用户ID,然后根据ID从数据库中查询出该用户专属的 token_secret,最后用这个密钥去验证Token的签名。由于旧Token是用旧密钥签发的,新密钥自然无法通过验证。

优缺点: * 优点:实现了真正的失效,无需黑名单。 * 缺点:实现逻辑更复杂,密钥管理成本更高。每次校验都需要先解析Payload再查询数据库获取密钥,流程相对繁琐,性能开销较大。

4. 采用双Token机制 (Access Token + Refresh Token)

这是一种更完善的认证策略,虽然不直接“失效”Access Token,但通过缩短其生命周期来降低风险,并提供平滑的刷新体验。

实现思路: * 签发Token:用户登录时,同时签发两个Token: * Access Token: 用于访问受保护资源,有效期很短(如5-15分钟)。 * Refresh Token: 用于获取新的Access Token,有效期较长(如7-30天)。 * 失效操作:当用户登出时,只需将长效的Refresh Token加入黑名单或从白名单中删除即可。由于Access Token生命周期极短,很快就会自然过期,即使被盗用,危害也有限。 * 校验与刷新: 1. 客户端每次请求携带Access Token。 2. 服务端验证Access Token,如果有效则处理请求。 3. 如果Access Token过期,客户端使用Refresh Token向特定的刷新接口请求新的Access Token。 4. 服务端验证Refresh Token的有效性(查询数据库中的状态),如果有效则签发新的Access Token返回给客户端。

优缺点: * 优点:兼顾了安全性和用户体验。短期的Access Token降低了泄露风险,长期的Refresh Token避免了用户频繁登录。服务端只需管理Refresh Token的状态,查询频率远低于管理Access Token。 * 缺点:实现复杂度最高,需要客户端与服务端配合处理Token的刷新逻辑。