如何在 Nestjs 中刷新令牌

Posted

技术标签:

【中文标题】如何在 Nestjs 中刷新令牌【英文标题】:How to refresh token in Nestjs 【发布时间】:2019-04-17 04:20:09 【问题描述】:
import  ExtractJwt, Strategy  from 'passport-jwt';
import  AuthService  from './auth.service';
import  PassportStrategy  from '@nestjs/passport';
import  Injectable, UnauthorizedException  from '@nestjs/common';
import  JwtPayload  from './model/jwt-payload.model';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) 
  constructor(private readonly authService: AuthService) 
    super(
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'secretKey',
    );
  

  async validate(payload: JwtPayload) 
    const user = await this.authService.validateUser(payload);
    if (!user) 
      throw new UnauthorizedException();
    
    return true;
  

令牌是由PassportStrategy从请求中提取的。当令牌过期或无效时,我不知道如何捕捉错误。我的目的是如果因为令牌过期而出现错误,我需要刷新令牌。否则做别的事。

【问题讨论】:

【参考方案1】:

可以在自定义身份验证保护中的canActivate 方法中处理刷新令牌实现。

如果访问令牌过期,刷新令牌将用于获取新的访问令牌。在这个过程中,刷新令牌也会更新。

如果两个令牌都无效,cookie 将被清除。

@Injectable()
export class CustomAuthGuard extends AuthGuard('jwt') 
  private logger = new Logger(CustomAuthGuard.name);

  constructor(
    private readonly authService: AuthService,
    private readonly userService: UserService,
  ) 
    super();
  

  async canActivate(context: ExecutionContext): Promise<boolean> 
    const request = context.switchToHttp().getRequest();
    const response = context.switchToHttp().getResponse();

    try 
      const accessToken = ExtractJwt.fromExtractors([cookieExtractor])(request);
      if (!accessToken)
        throw new UnauthorizedException('Access token is not set');

      const isValidAccessToken = this.authService.validateToken(accessToken);
      if (isValidAccessToken) return this.activate(context);

      const refreshToken = request.cookies[REFRESH_TOKEN_COOKIE_NAME];
      if (!refreshToken)
        throw new UnauthorizedException('Refresh token is not set');
      const isValidRefreshToken = this.authService.validateToken(refreshToken);
      if (!isValidRefreshToken)
        throw new UnauthorizedException('Refresh token is not valid');

      const user = await this.userService.getByRefreshToken(refreshToken);
      const 
        accessToken: newAccessToken,
        refreshToken: newRefreshToken,
       = this.authService.createTokens(user.id);

      await this.userService.updateRefreshToken(user.id, newRefreshToken);

      request.cookies[ACCESS_TOKEN_COOKIE_NAME] = newAccessToken;
      request.cookies[REFRESH_TOKEN_COOKIE_NAME] = newRefreshToken;

      response.cookie(ACCESS_TOKEN_COOKIE_NAME, newAccessToken, COOKIE_OPTIONS);
      response.cookie(
        REFRESH_TOKEN_COOKIE_NAME,
        newRefreshToken,
        COOKIE_OPTIONS,
      );

      return this.activate(context);
     catch (err) 
      this.logger.error(err.message);
      response.clearCookie(ACCESS_TOKEN_COOKIE_NAME, COOKIE_OPTIONS);
      response.clearCookie(REFRESH_TOKEN_COOKIE_NAME, COOKIE_OPTIONS);
      return false;
    
  

  async activate(context: ExecutionContext): Promise<boolean> 
    return super.canActivate(context) as Promise<boolean>;
  

  handleRequest(err, user) 
    if (err || !user) 
      throw new UnauthorizedException();
    

    return user;
  

将用户附加到请求是在JwtStrategy类的validate方法中完成的,如果访问令牌有效,它将被调用

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) 
  constructor(
    readonly configService: ConfigService,
    private readonly userService: UserService,
  ) 
    super(
      jwtFromRequest: cookieExtractor,
      ignoreExpiration: false,
      secretOrKey: configService.get('jwt.secret'),
    );
  

  async validate( id ): Promise<User> 
    const user = await this.userService.get(id);
    if (!user) 
      throw new UnauthorizedException();
    

    return user;
  

自定义 cookie 提取器示例

export const cookieExtractor = (request: Request): string | null => 
  let token = null;
  if (request && request.signedCookies) 
    token = request.signedCookies[ACCESS_TOKEN_COOKIE_NAME];
  
  return token;
;

【讨论】:

这不会将用户附加到请求中 将用户附加到请求是在 validate 类中的 JwtStrategy 方法中完成的,我为此添加了一个示例 cookieExtractors 来自哪里? cookieExtractor 是一个自定义提取器,用它更新示例【参考方案2】:

您可以创建自己的AuthGuard 并覆盖请求处理程序,而不是使用内置的AuthGuard

@Injectable()
export class MyAuthGuard extends AuthGuard('jwt') 

  handleRequest(err, user, info: Error) 
    if (info instanceof TokenExpiredError) 
      // do stuff when token is expired
      console.log('token expired');
    
    return user;
  


根据您想要执行的操作,您还可以覆盖您有权访问请求对象的canActivate 方法。看看AuthGuard sourcecode。

【讨论】:

我不明白。我应该如何使用这种方法将新的访问令牌传递给客户端?能详细点吗? @Albert 这确实取决于您的具体要求,但也许这个thread 会有所帮助。 令牌过期错误从何而来?

以上是关于如何在 Nestjs 中刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何识别来自不同租户nestjs多租户jwt的jwt令牌

如何在nestjs graphql中实现用户保护

NestJS & Passport:更改用户密码时更改 JWT 令牌?

我在我的 nestJS 应用程序(使用 authGuard)中使用了 passport-jwt 身份验证策略,如何访问我的控制器中的令牌有效负载?

Angular 8:如何在没有刷新令牌的情况下刷新令牌

如何在前端和后端存储 JWT 刷新令牌?