本文目录导读:

- 方案一:使用黑名单(最直接,但需权衡性能)
- 方案二:版本号机制(更优雅,但需调整架构)
- 方案三:缩短过期时间 + 配合 Refresh Token(最常用)
- 紧急情况下的特例:强制让所有用户下线
- 最佳实践:组合策略
这是一个非常关键的安全问题,首先要明确一个核心事实:标准的JWT(JSON Web Token)是无状态的,这意味着,一旦服务器签发了一个JWT,在它自然过期之前,服务器端并没有一个“中心化列表”能立刻强制让它失效。
撤销JWT本质上是一个工程妥协,需要在架构上提前设计,以下是几种主流且有效的应对策略:
使用黑名单(最直接,但需权衡性能)
当发现令牌被窃取时,立即将该令牌的 jti(JWT ID,一个唯一标识符)或整个令牌加入一个服务端共享的“黑名单”中。
- 存储方式:推荐使用Redis,将令牌的
jti作为 Key,过期时间设为与该令牌的原始过期时间一致(自动清理)。 - 工作流程:
- 用户携带JWT发起请求。
- 服务器先进行常规的签名验证和过期时间检查。
- 额外步骤:在Redis中查询该令牌是否存在于黑名单中。
- 如果在黑名单中,拒绝访问,如果不在,放行。
- 优点:实现简单,可立即生效。
- 缺点:引入了一次额外的网络IO(通常是Redis查询),增加了请求延迟,同时需要维护Redis的高可用性。
版本号机制(更优雅,但需调整架构)
给用户或客户端维护一个“令牌版本号”,并将这个版本号嵌入JWT的Payload中。
- 实施步骤:
- 在数据库中为每个用户增加一个字段
token_version。 - 签发JWT时,将用户当前的
token_version写入JWT的payload(`{“ver": 3} )。 - 撤销操作:当发现令牌被窃取,立即在数据库中将该用户的
token_version增加1(例如从3改为4)。 - 验证逻辑:服务器验证JWT时,解析出payload中的
ver值,然后去数据库查询该用户当前的token_version,如果JWT中的版本号小于数据库中的版本号,则拒绝访问。
- 在数据库中为每个用户增加一个字段
- 优点:不需要维护黑名单,查询的是用户最新的版本号,逻辑清晰,可以一次性撤销该用户的所有历史令牌。
- 缺点:每次请求都需要查一次数据库/缓存来获取版本号(不过可以和用户信息查询合并)。
缩短过期时间 + 配合 Refresh Token(最常用)
这是现代应用中最普遍的做法,它不直接“撤销”旧令牌,而是让短命令牌自动失效。
- 策略:
- Access Token(JWT):过期时间设置很短,例如15分钟。
- Refresh Token:这是一个不透明的、存储在服务器端的令牌,带有更长的有效期(例如7天或30天)。
- 工作流程:
- 用户使用Access Token正常访问。
- Access Token过期后,客户端使用Refresh Token请求新的Access Token。
- 撤销操作:一旦发现某个Access Token被窃取,服务器可以立即做两件事:
- 拒绝该Refresh Token(将其加入黑名单)。
- 或者删除该Refresh Token(如果存储在数据库)。
- 效果:窃贼持有的Access Token最多只能使用15分钟,15分钟后,由于无法获取新的Access Token(因为Refresh Token已被禁用),他便无法继续访问。
- 优点:绝大部分时间里无需检查黑名单(只检查签名和过期时间),性能极高,安全性通过“短时效”得到了很好的保证。
- 缺点:处理逻辑稍复杂(需要处理令牌刷新),并且如果窃贼在15分钟内用窃取的令牌作恶,你依然需要承受这15分钟的风险。
紧急情况下的特例:强制让所有用户下线
如果发生了非常严重的安全事件(比如数据库泄露),可以:
- 改变服务器的JWT签名密钥(
secret key或private key)。 - 效果:所有基于旧密钥签发的JWT都会在签名验证环节立即失效,所有用户都必须重新登录。
最佳实践:组合策略
在真实的生产环境中,通常会组合使用上述方法:
- 日常防御:采用方案三(短过期时间 + Refresh Token),这样95%的请求不需要额外查询,性能最好。
- 主动撤销:对于方案三无法覆盖的“短时间窗口”,配合方案一(黑名单) 或 方案二(版本号) 进行补充,当用户主动“退出所有设备登录”时,触发方案二(版本号) 的升级。
- 关键操作:对于转账、删除数据等高风险操作,可以增加二级验证(如二次密码、短信验证码),要求用户即使有有效JWT也需要提供额外验证手段。
- 标准JWT无法被真正“撤销”,只能用工程手段使其失效。
- 最短路径:采用方案三(短Access Token + Refresh Token),这是业界经受考验的主流做法。
- 如需即时生效:增加方案一(基于Redis的黑名单)。
- 最高效的批量撤销:使用方案二(版本号机制)。
在出现令牌盗取时,第一反应应是用最短的时间调用你已部署的“版本号升级”或“黑名单”接口,如果之前没有准备,那么立即更改签名密钥是唯一能立刻阻止所有旧令牌生效的应急方法,但也意味着所有用户需要重新登录。