多设备登录场景下如何管理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
相关产品推荐
相关产品推荐

