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
相关产品推荐
相关产品推荐

