인프런 커뮤니티 질문&답변

rhkdtjd_12님의 프로필 이미지

작성한 질문수

[코드팩토리] [초급] NestJS REST API 백엔드 완전 정복 마스터 클래스 - NestJS Core

모든 Route 기본 Private로 만들고 IsPublic Annotation 작업하기

@IsPublic 어노테이션으로 RefreshTokenGuard에서 RefreshToken 검증을 하기 위한 코드 공유

24.01.29 17:33 작성

·

221

·

수정됨

0

// is-public.const.ts
export enum IsPublicEnum {
  ISPUBLIC = 'ISPUBLIC',
  ISREFRESHTOKEN = 'ISREFRESHTOKEN',
}
// is-public.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { IsPublicEnum } from '../const/is-public.const';

export const ISPUBLIC_KEY = 'is_public';

export const IsPublic = (status: IsPublicEnum) =>
  SetMetadata(ISPUBLIC_KEY, status);
// bearer-token.guard.ts
@Injectable()
export class BearerTokenGuard implements CanActivate {
  constructor(
    private readonly authService: AuthService,
    private readonly usersService: UsersService,
    public readonly reflector: Reflector,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest();

    const requiredPublic = this.reflector.getAllAndOverride(ISPUBLIC_KEY, [
      context.getHandler(),
      context.getClass,
    ]);

    if (requiredPublic) {
      req.requiredPublic = requiredPublic;
    }

    if (requiredPublic === IsPublicEnum.ISPUBLIC) {
      return true;
    }

    const rawToken = req.headers['authorization'];

    if (!rawToken) throw new UnauthorizedException('토큰이 없습니다!');

    const token = this.authService.extractTokenFromHeader(rawToken, true);

    const result = await this.authService.verifyToken(token);

    
    const user = await this.usersService.getUserByEmail(result.email);

    req.user = user;
    req.token = token;
    req.tokenType = result.type;

    return true;
  }
}
  • 추가된 코드

if (requiredPublic) {
  req.requiredPublic = requiredPublic;
}

requiredPublic 있을때만 req에 담아줌.

  • 추가된 코드

if (requiredPublic === IsPublicEnum.ISPUBLIC) {
      return true;
    }

IsPublic일때만 return true 즉, ISREFRESHTOKEN 일때는 아래 로직들이 정상적으로 실행됨.

// bearer-token.guard.ts
@Injectable()
export class AccessTokenGuard extends BearerTokenGuard {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    await super.canActivate(context);

    const req = context.switchToHttp().getRequest();

    if (
      req.requiredPublic === IsPublicEnum.ISPUBLIC ||
      IsPublicEnum.ISREFRESHTOKEN
    ) {
      return true;
    }

    if (req.tokenType !== 'access') {
      throw new UnauthorizedException('Access Token이 아닙니다.');
    }

    return true;
  }
}
  • 추가된 코드

if (req.requiredPublic === IsPublicEnum.ISPUBLIC || IsPublicEnum.ISREFRESHTOKEN) {
   return true;
}

req.requiredPublic이 ISPUBLIC이거나 ISREFRESHTOKEN이면 return true로 global accessTokenGuard return true로 빠져나옴.

// bearer-token-guard.ts
@Injectable()
export class RefreshTokenGuard extends BearerTokenGuard {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    await super.canActivate(context);

    const req = context.switchToHttp().getRequest();

    if (req.tokenType !== 'refresh') {
      throw new UnauthorizedException('Refresh Token이 아닙니다.');
    }

    return true;
  }
}

변경 사항 없음. 왜냐하면 위에서 정상적으로 refreshToken을 verify하고 req에 값이 담겼기 때문에 RefreshTokenGuard가 정상적으로 실행 되어야함.

 

  • auth.controller에 적용하기

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @IsPublic(IsPublicEnum.ISREFRESHTOKEN)
  @Post('token/access')
  @UseGuards(RefreshTokenGuard)
  postTokenAccess(@Headers('authorization') rawToken: string) {
  
  ...
  
  }

  @IsPublic(IsPublicEnum.ISREFRESHTOKEN)
  @Post('token/refresh')
  @UseGuards(RefreshTokenGuard)
  postTokenRefresh(@Headers('authorization') rawToken: string) {

  ...

  }

  @IsPublic(IsPublicEnum.ISPUBLIC)
  @Post('login/email')
  @UseGuards(BasicTokenGuard)
  postLoginEmail(@Headers('authorization') rawToken: string) {

  ...

  }

  @IsPublic(IsPublicEnum.ISPUBLIC)
  @Post('register/email')
  postRegisterEmail(@Body() body: RegisterUserDto) {

 ... 

 }
}

답변 2

1

코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

2024. 01. 30. 10:10

안녕하세요!

공유 감사합니다!

rhkdtjd_12님의 프로필 이미지
rhkdtjd_12
질문자

2024. 01. 30. 11:58

😃

0

준호리님의 프로필 이미지

2024. 02. 24. 03:29

안녕하세요 혹시 AccessTokenGuard를 전역적으로 지정해준 상태에서

@isPublic , @UseGuards(RefreshTokenGuard) 어노테이션이 적용된 api가 리프레쉬가드를 못타는 문제 때문에 코드 수정하신거 맞으신가요?

rhkdtjd_12님의 프로필 이미지
rhkdtjd_12
질문자

2024. 02. 24. 13:57

네 맞습니다. 기존 코드는 RefreshToken의 validation을 하지 않고 return true를 해주기 때문입니다.