JWT如何避免多端登录

JWT 如何避免多端登录——最新 token 映射、Redis 方案、版本号优化

问题背景

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

难点在于 JWT 无状态——服务端没存会话,没法主动让旧 token 失效,必须引入额外机制实现账号互斥。

方案:账号 → 最新 Token 映射

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

验签通过 → 查映射表 → token 等于最新?放行
                     → 否则返回 401-Kicked,客户端弹窗"该账号已在其他设备登录"
单账号互斥流程:后登录覆盖 Redis 中的最新 token,旧设备请求返回 401 被踢
图 1:后登录覆盖最新 token,旧设备被踢

存储方案有两个:

方案 A:Redis 缓存

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

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

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

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

两种存储方案:网关集群共享 Redis 强一致,与节点本地缓存 Gossip 同步最终一致
图 2:Redis 共享 vs 节点本地缓存 + 同步

方案对比

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

关键设计细节

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

落地方案

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

滚动至顶部