🚀 프로젝트/🎸 고스락 티켓

[Gosrock/Nestjs] Guard 사용중인 Controller 내부 특정 메소드에 모든 접근 허가하기 (NoAuth)

gengminy 2022. 8. 17. 13:09

고스락 티켓 예매 페이지 22th 의 일부인

@NoAuth 데코레이터 구현에 관한 글입니다

 

우선 이 글은 Nestjs 에서

AuthGuard 등의 Custom Guard 구현에 대해 알고 있다고 가정하고 작성했습니다

 

 

📝 Reference

Guards - https://jakekwak.gitbook.io/nestjs/overview/guards

 

 

🔍 문제 상황

우선 고스락 티켓 프로젝트에서는 AccessTokenGuard 라는 커스텀 가드를 구현했고

이는 Role 기반으로 엑세스 토큰에서 유저와 그 권한을 뽑아와서

해당 요청 메소드에 접근 권한이 있는지 확인하고 접근 인가 / 불가 처리를 하는 것이다

 

자세한 것은 고티켓 팀장님의 포스팅을 참고하십쇼

https://devnm.tistory.com/16?category=1258201 

 

[고스락 티켓 2.0] nest js 유저 role 기반 api 인가

 어노테이션과 메타데이터를 이용해서 , 유저가 Admin인지 일반 User인지에따른 api 인가를 설정해보도록 하자. 이글을 통해서 얻어갈 수 있는점 nestjs SetMetadata 를 통해서 어노테이션과, 메타데이

devnm.tistory.com

 

📝 tickets.controller.ts

@ApiTags('tickets')
@ApiBearerAuth('accessToken')
@Controller('tickets')
@UseGuards(AccessTokenGuard)
export class TicketsController {
  constructor(private ticketService: TicketsService) {}
  ...
  ...
 }

내가 맡았던 구현 부분 중 티켓 컨트롤러의 예시

@UseGuards 데코레이터를 통해 구현해둔 AccessTokenGuard 를 사용하도록 처리했다

 

문제는 이렇게 할 경우 중간에 특정 메소드만 권한을 부여하기가 어렵다는 것이다

 

사실 어렵지는 않지만

전역 가드를 떼어낸 후에 요청에 권한이 필요한 각 메소드마다 다시 가드를 붙이는 식으로

코드 구조가 쓸데없이 난잡해지고 귀찮아진다

개발자가 제일 싫어하는 구조? 라고 해야되나

 

그래서 나온 해결 책은 구조는 그대로 두되

새로운 데코레이터를 만드는 것이다

 

 

📌 @NoAuth

📝 NoAuth.guard.ts

import { SetMetadata } from '@nestjs/common';

export const NoAuth = () => SetMetadata('no-auth', true);

데코레이터를 하나 만드는 방법은 어렵지 않다

인자로 받은 애들을 특정 이름의 메타데이터로 변환 시켜주면 된다

 

이제 특정 메소드에 @NoAuth() 를 붙여주게 되면

해당 메소드의 메타데이터로 'no-auth': true 라는 값이 추가된다

 

 

🚀 코드 수정

📝 AccessToken.guard.ts

@Injectable()
export class AccessTokenGuard implements CanActivate {
  constructor(private authService: AuthService, private reflector: Reflector) {}

  canActivate(
    context: ExecutionContext
  ): boolean | Promise<boolean> | Observable<boolean> {
    //@NoAuth 사용시 해당 부분에서 AccessTokenGuard 사용 해제시킴
    const noAuth = this.reflector.get<boolean>('no-auth', context.getHandler());
    if (noAuth) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    return this.validateRequest(request, context);
  }

  private async validateRequest(request: Request, context: ExecutionContext) {
  ...
  ...
  ...
}

 

이제 프로젝트에서 사용중인 AuthGuard 라던지,,,

여기서는 AccessTokenGuard 같이 내가 사용중인 가드의 코드를 약간만 수정하면 된다

 

Nestjs 에서 커스텀 가드는 반드시 CanActivate 를 구현해야 한다

가드는 특정 요청을 가로챈 후에

CanActivate 의 값이 true 이면 요청을 승인하고 그렇지 않으면 거부한다

 

이제 ExecutionContext 에서 getHandler 로

AccessTokenGuard를 사용중인 컨트롤러에서 메소드의 참조값을 가져온다

이후 리플렉터로 여기서 우리가 구현했던 메타데이터인 'no-auth' 값을 뽑아온다

 

getHandler 로 가져온 메소드가 @NoAuth() 데코레이터를 사용중이라면

'no-auth': true 일 것이고 그렇지 않다면 undefined, 즉 false 이다

 

'no-auth': true 이면 가드를 즉시 통과시키고

그렇지 않다면 원래처럼 가드에서 인증을 처리하면 된다

 

 

 

🚀 NoAuth 적용

📝 tickets.controller.ts

...
  @ApiOperation({
    summary: '[랜딩페이지] 티켓 개수를 반환한다'
  })
  @ApiResponse({
    status: 200,
    description: '요청 성공시',
    type: TicketCountDto
  })
  @NoAuth()
  @Get('/count')
  async getTicketCount() {
    const count = await this.ticketService.countTicket();
    return { count: count };
  }
...

@NoAuth() 를 필요로 하는 메소드에 적용시켰다

이 메소드는 GET /count 요청을 날렸을 때

DB 내부에 몇 장의 티켓이 있는지 반환해주는 메소드이다

 

랜딩페이지에서 티켓이 몇 개 주문됐는지 보여줄 필요가 있는데

로그인하지 않은 사용자도 이를 볼 수 있어야 하기 때문에 구현하게 되었다

 

 

바로 이 부분을 위해서...

 

구현은 알고보면 매우 간단하지만

Nestjs 의 가드와 데코레이터의 동작 원리를 이해하지 못했다면 상당히 복잡해 보일 것이다

 

아무쪼록 더 까먹기 전에

얼른 구현하면서 공부했던 것들을 정리해야겠다

 

사이트가 궁금하다면~~~

 

https://gosrock.band/

 

고스락 티켓

22번째 정기공연 [We are GOSROCK, Invites you]

gosrock.band