【问题标题】:How to refresh token in Nestjs如何在 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从请求中提取的。当令牌过期或无效时,我不知道如何捕捉错误。我的目的是如果因为令牌过期而出现错误,我需要刷新令牌。否则做别的事。

【问题讨论】:

    标签: javascript node.js typescript passport.js nestjs


    【解决方案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 会有所帮助。
    • 令牌过期错误从何而来?
    猜你喜欢
    • 1970-01-01
    • 2020-05-13
    • 2019-10-06
    • 1970-01-01
    • 2021-06-19
    • 2020-07-12
    • 2017-05-29
    • 2020-09-21
    • 2021-08-25
    相关资源
    最近更新 更多