如何基于JWT实现多设备同时登录的令牌管理(NestJS后端)
问题根源
你当前的设计将refresh token与用户表记录做1:1强绑定,本质是面向单设备登录的模型,多设备场景下token互相覆盖是模型的天然缺陷,和接口逻辑代码无关。
工业界标准落地方案
1. 重构refresh token存储模型
绝对不要把多个设备的token存在用户表的单个字段、甚至是用户表字段存JSON数组,后者会带来查询效率低、无法加索引、并发写丢数据、过期清理困难等一系列生产问题。
单独新建user_refresh_tokens关联表,和用户表做多对一映射(一个用户对应多条有效token记录,每条记录对应一个登录设备),核心字段如下:
id:表主键user_id:关联用户表的外键,必须加普通索引device_id:客户端持久化存储的唯一设备标识(web端可首次访问时生成随机UUID存localStorage,移动端直接取系统级设备UUID),同设备所有登录、刷新请求固定携带该标识token_hash:存储refresh token的哈希值,禁止存明文expires_at:refresh token过期时间戳issued_at:token签发时间ip:token签发时的客户端IP,可选,用于安全审计user_agent:token签发时的客户端UA,可选,用于前端展示登录设备信息is_revoked:布尔值,标记token是否被手动吊销,默认值为false
2. 调整登录接口(/api/login)逻辑
- 校验用户名、密码合法性通过后,要求客户端必须携带
device_id参数 - 调用jwt库分别生成access token、refresh token,签发refresh token时payload固定携带
sub: userid、deviceId: 客户端传入的device_id、jti: 随机生成的UUID三个核心字段 - 对生成的refresh token用bcrypt/argon2做哈希(和密码哈希的安全等级一致,禁止用MD5、SHA256等快速哈希算法)
- 查询
user_refresh_tokens表中是否存在user_id = 当前用户ID且device_id = 客户端传入设备ID的记录:- 若存在记录:更新该条记录的
token_hash、expires_at、ip、user_agent字段,覆盖同设备旧的token信息,避免同设备重复登录产生冗余记录 - 若不存在记录:插入一条新的token记录
- 若存在记录:更新该条记录的
- 将access token、refresh token返回给客户端
3. 调整令牌刷新接口(/api/refresh)逻辑
- 接口要求客户端同时提交refresh token明文、当前设备的
device_id - 先校验refresh token的JWT格式合法性、是否已过JWT自身的有效期,从payload中提取
userid、deviceId、jti - 到
user_refresh_tokens表查询匹配以下条件的记录:user_id = 提取的用户ID、device_id = 客户端提交的设备ID、is_revoked = false、expires_at > 当前服务器时间 - 用客户端提交的refresh token明文,和查询到的记录中的
token_hash做哈希校验:- 校验不通过直接返回401状态码,可配套安全策略:同一账号短时间内出现多次校验失败时,触发异常登录告警
- 校验通过后,生成新的access token、新的refresh token
- 更新当前匹配到的token记录:将
token_hash替换为新refresh token的哈希值,若业务用滑动过期策略则同步延长expires_at,固定过期策略则保持原过期时间不变 - 将新生成的两个令牌返回给客户端
4. 配套必备能力
- 单设备登出:调用
/api/logout接口时,直接将当前user_id + device_id对应的记录is_revoked字段设为true,仅下线当前设备,不影响其他已登录设备 - 登录设备管理:提供查询接口,返回当前账号下所有有效token对应的设备信息(UA、登录IP、登录时间),支持用户手动指定设备下线,本质就是将对应记录的
is_revoked设为true - 过期数据清理:配置每日定时任务,物理删除表中
expires_at早于当前时间超过30天、或is_revoked = true超过7天的记录,避免表数据无限膨胀 - 高危操作联动:用户修改密码、主动触发"下线所有其他设备"操作时,批量将当前账号下除当前设备外的所有token记录
is_revoked设为true,强制其他设备重新登录 - 泄露防护:如果刷新接口时出现token哈希校验不通过的情况,直接将当前匹配到的device_id对应记录标记为吊销,避免被窃取的token被恶意利用
NestJS落地注意点
- 用
@nestjs/jwt签发token时,access token建议设置15-30分钟有效期,refresh token建议设置7-30天有效期,根据业务安全等级调整 - refresh token的payload不要存敏感信息,JWT payload仅做Base64编码,可被直接解码,敏感数据必须存在服务端数据库
- 不要把refresh token的校验逻辑全写在控制器里,封装成独立的Passport Strategy复用逻辑,和现有JWT鉴权体系保持一致
内容的提问来源于stack exchange,提问作者Kunal Karmakar
相关产品推荐
相关产品推荐

