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

NestJS结合Passport如何在用户修改密码时更换JWT令牌强制全设备登出

实现方案说明

JWT是无状态凭证,服务端默认不存储已签发的令牌,所以无法直接主动作废之前签发的有效令牌,需要额外扩展校验逻辑实现你的需求。

方案1:令牌版本校验(推荐,轻量无额外存储依赖)

这个方案不需要维护黑名单,仅需要在用户表新增一个字段即可实现全设备强制登出:

实现步骤:

  • 给User实体新增tokenVersion字段,类型为数字,默认值为1,用来标记当前用户有效的令牌版本
    // User实体类新增字段
    @Column({ default: 1 })
    tokenVersion: number;
    
  • 修改你的changePassword方法,密码更新时同步把tokenVersion加1,标记所有旧版本令牌失效
    async changePassword(uId: string, password: any) {
      return await this.userRepository.createQueryBuilder()
        .update(User)
        .set({ 
          password: await bcrypt.hash(password.value, 10),
          // 数据库层面自增版本号,避免并发问题
          tokenVersion: () => "token_version + 1" 
        })
        .where("userId = :userId", { userId: uId })
        .execute();
    }
    
  • 调整JWT签发逻辑,将tokenVersion写入JWT的payload中
    // AuthService的登录签发令牌逻辑示例
    async generateToken(user: User) {
      const payload = {
        sub: user.userId,
        username: user.username,
        // 写入当前令牌版本
        tokenVersion: user.tokenVersion
      }
      return this.jwtService.sign(payload);
    }
    
  • 扩展JWT校验守卫,在校验JWT签名合法后,额外对比令牌版本和数据库中用户当前的版本是否一致,不一致直接返回未授权
    import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    import { UserService } from './user.service';
    
    @Injectable()
    export class CustomJwtAuthGuard extends AuthGuard('jwt') {
      constructor(private userService: UserService) {
        super();
      }
    
      async canActivate(context: ExecutionContext): Promise<boolean> {
        // 先执行默认的JWT签名、过期时间校验
        const baseValid = await super.canActivate(context) as boolean;
        if (!baseValid) return false;
    
        const req = context.switchToHttp().getRequest();
        const jwtPayload = req.user;
        // 查询当前用户最新的版本号
        const currentUser = await this.userService.findOneById(jwtPayload.sub);
        // 版本不一致说明令牌已失效
        if (jwtPayload.tokenVersion !== currentUser.tokenVersion) {
          throw new UnauthorizedException('当前会话已失效,请重新登录');
        }
        return true;
      }
    }
    
  • 把项目中原有使用@UseGuards(AuthGuard('jwt'))的地方替换为@UseGuards(CustomJwtAuthGuard)即可。

无需修改表结构的替代实现

如果不想新增字段,也可以在签发JWT时将用户密码哈希的前8位写入payload,校验时对比该片段即可,修改密码后哈希片段会自动变化,无需额外维护版本字段。

方案2:JWT黑名单(适合更灵活的作废场景)

如果需要支持单独作废某一个设备的令牌,可以用Redis维护失效JWT的黑名单:

  • 密码修改时,将该用户当前所有已签发的令牌(需要你提前在用户登录时存储用户和对应令牌的关联关系)加入黑名单,过期时间设置为JWT本身的有效时长
  • JWT校验时先查Redis中是否存在该令牌,存在则直接返回未授权
  • 该方案需要额外的Redis存储开销,仅当你需要更细粒度的令牌管控时使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 18:27:03