JWT如何避免多端登录

问题背景

游戏项目使用 HTTP 短连接网关 + JWT 鉴权。JWT 无状态,网关可以轻松分布式部署,但随之而来的问题是多端登录:需求是单账号只允许最后登录的设备在线,旧设备要被踢下线。

挑战在于:JWT 无状态,服务端无法主动让旧 token 失效——必须引入额外机制实现账号互斥。

方案:账号 → 最新 Token 映射

核心思路:维护一张映射表,key 为账号,value 为该账号最新签发的 token(或版本号)。网关每收到请求:

验签通过 → 查映射表 → token 等于最新?放行
                     → 否则返回 401-Kicked,客户端弹窗"该账号已在其他设备登录"

存储方案有两个:

方案 A:Redis 缓存

// 登录时写入最新 token(SET + EXPIRE 原子完成)
SET account:<uid> <token> EX 86400

// 请求时比较
token == GET account:<uid> ? 放行 : 踢下线

方案 B:所有网关进程内同步缓存

每个网关本地缓存一份完整映射,通过 Gossip 等协议同步。

方案对比

维度Redis 方案进程内缓存同步
一致性强一致(所有节点读同一 Redis)最终一致(同步延迟造成短暂不一致)
扩展性✅ 新节点直连 Redis 即可❌ 新节点需全量同步,规模大开销高
故障恢复✅ 持久化,重启不丢❌ 节点宕机丢数据,需重新同步
实现复杂度✅ 简单(SET+EXPIRE)❌ 需实现同步协议
性能依赖 Redis 吞吐(需集群)本地读取快,但同步有网络开销
外部依赖需维护 Redis 高可用无外部依赖

关键设计细节

  • 存版本号而非完整 token:token 里附加递增版本号(如签发时间戳),映射表只存版本号——内存占用小,比较更快。请求时校验 JWT 内的版本号与映射表一致;
  • 登录互斥:新登录时更新映射(Redis SET / 广播同步),旧 token 立即失效;
  • 客户端响应:收到 401-Kicked 弹”账号异地登录”并跳登录页;
  • 容灾降级:Redis 故障时短暂降级为”允许多设备登录”(牺牲安全性保可用性),比全部拒绝更友好;
  • 键过期:映射键设置与 token 有效期一致的 TTL,防止死账号数据堆积。

落地方案

最终选择了 Redis 缓存:实现简单、强一致、无扩展包袱,代价是引入一个 Redis 依赖(游戏网关本身常用 Redis,边际成本低)。

滚动至顶部