文章目录
- 无状态 JWT Token 下控制账号状态的实现方案
- 一、背景与问题
- 二、传统解决方案及其局限性
- 2.1 方案一:Token 存入 Redis
- 2.2 方案二:缩短 Token 过期时间
- 三、推荐方案:Access Token + Refresh Token 双令牌机制
- 3.1 核心思路
- 3.2 正常认证流程
- 3.3 账号禁用场景的时序图
- 3.4 账号状态控制的核心逻辑
- 3.5 账号状态检查流程图
- 四、方案优缺点分析
- 4.1 优点
- 4.2 缺点
- 4.3 延迟问题分析
- 五、与 Redis 方案对比
- 六、总结
- 参考资料
无状态 JWT Token 下控制账号状态的实现方案
一、背景与问题
在现代微服务架构中,JWT(JSON Web Token)因其无状态、自包含的特性,成为身份认证的主流方案。JWT 的无状态特性意味着服务端不需要存储任何会话信息,每个请求携带的 Token 本身就包含了所有必要的认证信息,服务端只需要验证签名即可。
然而,这种无状态特性也带来了一个棘手的问题:当账号状态发生变化时(如账号被禁用、权限被修改、强制下线等),服务端无法主动使已签发的 Token 失效。
这意味着即使管理员禁用了某个账号,该账号已获取的 Token 在过期前仍然可以正常使用,这对安全性要求较高的场景来说是一个严重隐患。
二、传统解决方案及其局限性
2.1 方案一:Token 存入 Redis
最常见的解决方案是将 JWT Token 缓存到 Redis 中,在每次请求时检查 Token 是否存在于 Redis 中:
- 登录成功 → JWT Token 存入 Redis(设置相同过期时间)
- 请求到达 → 验证 JWT 签名 → 检查 Redis 中是否存在该 Token
- 账号禁用 → 从 Redis 中删除该用户的 Token
局限性:这种方案破坏了 JWT 的无状态特性。原本 JWT 的设计初衷是让服务端无需存储会话状态,现在却需要维护 Redis 中的 Token 记录,增加了系统复杂度和 Redis 的依赖。如果 Redis 不可用,整个认证体系都会受到影响。
2.2 方案二:缩短 Token 过期时间
将 Token 的过期时间设置得极短(如 1 分钟),让 Token 自然快速过期。
局限性:用户体验极差,频繁需要重新登录,不适用于大多数业务场景。
三、推荐方案:Access Token + Refresh Token 双令牌机制
3.1 核心思路
签发两种 Token,通过不同的过期时间策略来平衡安全性和用户体验:
- Access Token(访问令牌):用于日常接口调用,过期时间设置较短(示例:5 分钟,实际可根据业务场景调整)
- Refresh Token(刷新令牌):专门用于获取新的 Access Token,过期时间较长(示例:17 小时,实际可根据业务场景调整)
关键设计点:在 Refresh Token 刷新 Access Token 的时候,校验账号状态。这样即使 Access Token 在有效期内,一旦账号状态发生变化,也能在下次刷新时被检测到。
3.2 正常认证流程
注:流程图中的时间参数(5分钟、17小时)仅为示例,实际项目中应根据业务需求灵活配置。
3.3 账号禁用场景的时序图
3.4 账号状态控制的核心逻辑
在 Refresh Token 刷新 Access Token 的时机,服务端需要执行以下步骤:
- 验证 Refresh Token 的签名和过期时间
- 提取账号标识信息(如 accountId)
- ★ 查询数据库检查账号状态(这是控制的关键)
- 账号是否存在
- 账号是否被禁用
- 权限是否发生变更
- 其他业务规则校验
- 账号状态正常:生成新的 Access Token 并返回
- 账号状态异常:抛出未授权异常,拒绝刷新
3.5 账号状态检查流程图
四、方案优缺点分析
4.1 优点
| 优点 | 说明 |
|---|---|
| 保持无状态 | JWT Token 本身仍然是无状态的,服务端不需要存储 Token 记录 |
| 及时控制 | 通过较短的 Access Token 过期时间(示例:5 分钟,可调整),确保账号状态变更能在合理时间内生效 |
| 用户体验好 | 在无异常情况下,用户可以在 Refresh Token 有效期内无感知使用 |
| 实现简单 | 不需要引入 Redis 等额外组件,只需在刷新接口添加账号状态校验 |
| 灵活可调 | Access Token 和 Refresh Token 的过期时间可根据业务场景灵活配置 |
4.2 缺点
| 缺点 | 说明 |
|---|---|
| 存在延迟 | 账号被禁用后,已签发的 Access Token 在过期前仍然有效(延迟时间 = Access Token 的过期时间,示例中使用 5 分钟) |
| 刷新接口需查询数据库 | 每次刷新都需要查询账号状态,增加了数据库压力(但相比 Redis 方案,这个查询是不可避免的) |
4.3 延迟问题分析
这是该方案最大的局限性,但也是保持无状态必须接受的权衡:
- 延迟时间 = Access Token 的过期时间(示例中使用 5 分钟,实际可调整)
- 意味着账号禁用后,已签发的 Access Token 在过期前仍然可用
不同业务场景下的时间配置建议:
| 场景 | 建议 Access Token 有效期 |
|---|---|
| 普通互联网应用 | 5-15 分钟(可接受) |
| 企业内部系统 | 5-30 分钟(可接受) |
| 金融/支付系统 | 1-2 分钟(需评估) |
| 高安全要求场景 | 考虑 Redis + 短过期时间组合方案 |
注:以上时间参数仅为参考示例,实际项目中应根据具体业务需求、安全要求和用户体验目标进行调整。
五、与 Redis 方案对比
| 维度 | Access/Refresh Token 方案 | Redis 缓存方案 |
|---|---|---|
| 无状态 | ✅ 保持无状态 | ❌ 需要存储 Token |
| 复杂度 | ✅ 简单,无需额外组件 | ❌ 依赖 Redis,增加复杂度 |
| 账号状态控制 | ⚠️ 有延迟(延迟 = Access Token 过期时间,可调整) | ✅ 实时生效 |
| 用户体验 | ✅ 无感知刷新 | ✅ 无感知刷新 |
| 数据库压力 | ⚠️ 刷新时查询账号状态 | ✅ 只查 Redis |
| 适用场景 | 大多数互联网应用 | 高安全要求的场景 |
| 灵活性 | ✅ 时间参数可灵活调整 | ⚠️ 依赖 Redis 配置 |
六、总结
在无状态 JWT 场景下控制账号状态,Access Token + Refresh Token 双令牌机制是一个平衡了无状态特性和账号控制能力的方案:
- Access Token 设置较短过期时间(示例:5 分钟,可根据业务调整),保证在正常情况下用户体验
- Refresh Token 设置较长过期时间(示例:17 小时,可根据业务调整),减少用户登录频率
- 在刷新 Access Token 时校验账号状态,实现账号禁用等场景的控制
- 接受一定的延迟(延迟时间 = Access Token 的过期时间,可调整),在大多数业务场景下是可接受的
如果你的业务场景对实时性要求极高(账号禁用必须立即生效),那么可以考虑JWT + Redis 组合方案,虽然破坏了无状态性,但能获得实时的控制能力。
架构设计的本质就是在各种约束下做权衡(Trade-off)。理解每种方案的优缺点,根据实际业务场景做出合适的选择,才是优秀架构师应有的能力。
参考资料
- JWT 官方规范
- OAuth 2.0 规范 - Refresh Token
- RFC 6749 - The OAuth 2.0 Authorization Framework