You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多设备登录场景下如何管理refreshToken?

多设备场景下的RefreshToken管理方案(NestJS)

1. 调整存储结构:按「用户+设备」维度存储

将原有的userId → 单个token结构,改为userId → 设备Token列表,每个Token关联唯一的设备标识(比如前端生成的设备UUID、浏览器UA+IP哈希值)。这样每个设备拥有独立的RefreshToken,新设备登录不会覆盖旧设备的Token。

  • 数据库表设计示例(基于TypeORM):
    @Entity()
    export class RefreshToken {
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column()
      userId: string;
    
      @Column({ unique: true })
      token: string;
    
      @Column()
      deviceId: string; // 设备唯一标识,由前端生成或后端解析生成
    
      @Column({ type: 'timestamp' })
      expiresAt: Date;
    
      @Column({ default: false })
      isRevoked: boolean; // 标记Token是否已失效
    }
    

2. 登录流程优化:为每个设备生成独立Token

用户登录时,无论是否已有其他设备在线,都生成新的RefreshToken,并关联当前设备标识存入数据库:

  • NestJS AuthService核心逻辑示例:
    async login(user: User, deviceId: string) {
      // 生成AccessToken
      const accessToken = this.jwtService.sign({ userId: user.id });
      
      // 生成RefreshToken,携带设备标识
      const refreshToken = this.jwtService.sign(
        { userId: user.id, deviceId },
        { expiresIn: '7d' }
      );
    
      // 存入数据库
      await this.refreshTokenRepository.save({
        userId: user.id,
        token: refreshToken,
        deviceId,
        expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
      });
    
      return { accessToken, refreshToken };
    }
    

3. 刷新Token流程:校验设备+Token有效性

用户刷新AccessToken时,需验证RefreshToken属于当前设备且未失效:

  • 刷新逻辑示例:
    async refresh(refreshToken: string, deviceId: string) {
      // 解析RefreshToken
      const payload = this.jwtService.verify(refreshToken);
      
      // 查询数据库中对应的Token记录
      const tokenRecord = await this.refreshTokenRepository.findOne({
        where: { token: refreshToken, userId: payload.userId, deviceId },
      });
    
      if (!tokenRecord || tokenRecord.isRevoked || tokenRecord.expiresAt < new Date()) {
        throw new UnauthorizedException('无效的RefreshToken');
      }
    
      // 生成新的AccessToken
      const newAccessToken = this.jwtService.sign({ userId: payload.userId });
      
      // 可选:刷新时生成新的RefreshToken替换旧的,提升安全性
      const newRefreshToken = this.jwtService.sign(
        { userId: payload.userId, deviceId },
        { expiresIn: '7d' }
      );
      tokenRecord.token = newRefreshToken;
      tokenRecord.expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
      await this.refreshTokenRepository.save(tokenRecord);
    
      return { accessToken: newAccessToken, refreshToken: newRefreshToken };
    }
    

4. 设备管理与Token失效

支持用户查看所有登录设备,并实现单个/批量Token失效:

  • 获取用户登录设备列表:

    async getUserDevices(userId: string) {
      return this.refreshTokenRepository.find({
        where: { userId, isRevoked: false },
        select: ['deviceId', 'expiresAt'],
      });
    }
    
  • 失效单个设备的Token:

    async revokeDeviceToken(userId: string, deviceId: string) {
      await this.refreshTokenRepository.update(
        { userId, deviceId, isRevoked: false },
        { isRevoked: true }
      );
    }
    
  • 批量失效用户所有设备的Token(如修改密码时):

    async revokeAllUserTokens(userId: string) {
      await this.refreshTokenRepository.update(
        { userId, isRevoked: false },
        { isRevoked: true }
      );
    }
    

5. 前端配合:传递设备标识

前端需要在登录、刷新Token时传递设备标识:

  • 首次打开应用时,生成唯一设备UUID并存储在localStorage或sessionStorage
  • 登录、刷新请求时,将该UUID放在请求头(如X-Device-Id)或请求体中

6. 额外优化建议

  • 自动清理过期Token:用NestJS的@nestjs/schedule定时清理数据库中已过期或已失效的Token记录,减少冗余。
  • Token哈希存储:数据库中存储RefreshToken的哈希值(如bcrypt加密),避免明文泄露风险。
  • 设备标识增强:结合浏览器UA、IP等信息生成更唯一的设备标识,防止同一设备多次生成不同ID。

内容的提问来源于stack exchange,提问作者user10874312

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 08:30:50