[Nestjs Study] 7. Guards

hiio420.official·2025년 6월 13일

nestjs

목록 보기
7/7


가드는 인터페이스를 구현하는 데코레이터 @Injectable()로 주석이 달린 클래스입니다.

가드는 단일 책임을 갖습니다 . 런타임에 존재하는 특정 조건(권한, 역할, ACL 등)에 따라 주어진 요청을 경로 처리기가 처리할지 여부를 결정합니다. 이를 권한 부여(authorization)라고 합니다 . 권한 부여(authorization)와 그 사촌격인 인증 (authentication)은 기존 Express 애플리케이션에서 일반적으로 미들웨어 가 처리해 왔습니다 . 토큰 검증이나 객체에 속성 연결과 같은 작업은 request특정 경로 컨텍스트(및 해당 메타데이터)와 긴밀하게 연결되지 않기 때문에 미들웨어는 인증에 적합한 선택입니다.

next()함수를 호출한 후 어떤 핸들러가 실행될지 알 수 없습니다. 반면, 가드는 인스턴스 에 접근하여 ExecutionContext다음에 무엇이 실행될지 정확히 알고 있습니다. 가드는 예외 필터, 파이프, 인터셉터와 마찬가지로 요청/응답 주기의 정확한 시점에 처리 로직을 삽입할 수 있도록 설계되었으며, 이를 선언적으로 수행할 수 있습니다. 이는 코드를 DRY하고 선언적으로 유지하는 데 도움이 됩니다.


Q1. NestJS의 Guard란 무엇인가요? 언제 사용하나요?

A1.
Guard는 NestJS에서 요청(Request)을 컨트롤러에 전달하기 전에 실행되는 클래스입니다.
주로 인증(Authentication) 또는 권한(Authorization)과 같은 조건을 검증할 때 사용됩니다.
예를 들어, 사용자가 특정 API에 접근 가능한지를 판단할 때 Guard를 설정하면, 조건이 맞지 않으면 요청 자체를 차단할 수 있어요.


Q2. Guard를 구현할 때 어떤 인터페이스와 메서드를 사용하나요?

A2.
Guard는 @Injectable() 데코레이터가 붙은 클래스이며, CanActivate 인터페이스를 구현해야 합니다.
핵심은 canActivate(context: ExecutionContext): boolean | Promise<boolean> 메서드입니다.
이 메서드가 true를 반환하면 요청이 통과되고, false를 반환하면 요청이 거부됩니다.


Q3. ExecutionContext는 어떤 역할을 하나요?

A3.
ExecutionContext는 현재 실행 중인 요청의 정보를 추상화한 객체입니다.
context.switchToHttp().getRequest()를 통해 HTTP 요청 객체에 접근할 수 있어요.
RPC, GraphQL, WebSocket 등 다양한 플랫폼에서도 사용할 수 있도록 추상화되어 있어 유연성이 큽니다.


Q4. Guard와 Interceptor, Middleware의 차이는 무엇인가요?

A4.
이 셋은 모두 요청 처리 흐름에서 관여하지만 시점과 목적이 달라요.

  • Middleware: 요청이 애플리케이션에 들어오기 전에 실행. 주로 로깅, 요청 변형, CORS 처리 등.
  • Guard: 요청이 컨트롤러에 도달하기 직전에 실행. 인증/인가 로직에 최적화.
  • Interceptor: 컨트롤러 실행 전/후에 실행. 응답 가공, 캐싱, 트랜잭션 처리 등에 사용.

Q5. Guard는 어디에 적용할 수 있나요?

A5.
Guard는 다음과 같이 다양한 범위에 적용할 수 있어요.

  • 메서드 단위: @UseGuards(MyGuard)
  • 컨트롤러 전체: @UseGuards(MyGuard)를 컨트롤러 클래스에
  • 전역 적용: app.useGlobalGuards()main.ts에서 설정

Q6. Role 기반 접근 제어는 어떻게 구현하나요?

A6.
주로 커스텀 데코레이터 @Roles()RolesGuard를 조합해서 사용합니다.

  1. @Roles() 데코레이터로 필요한 역할을 지정하고
  2. RolesGuard에서 사용자의 역할이 그에 포함되는지를 검사하는 방식입니다.

이런 구조는 NestJS의 메타데이터(Reflector)와 Guard의 궁합을 잘 보여주는 예시죠.


Q7. NestJS Guard를 사용하면서 겪은 어려움이나 깨달음이 있다면?

A7.
처음에는 ExecutionContext 구조나 HTTP 외의 컨텍스트 전환 방식이 헷갈릴 수 있었어요.
하지만 구조를 하나하나 뜯어보고 나면 NestJS의 확장성과 철학이 잘 드러나는 부분이기도 합니다.
특히 인증 시스템과 역할 기반 인가를 설계할 때 Guard는 핵심 역할을 하기 때문에, 실제 프로젝트에서도 Guard의 잘 설계된 구조는 시스템 안정성과 보안성에 큰 도움이 되었습니다.


권한 부여 가드

권한 부여는 Guards의 훌륭한 사용 사례입니다. 특정 경로는 호출자(일반적으로 특정 인증된 사용자)가 충분한 권한을 가지고 있을 때만 사용할 수 있어야 하기 때문입니다. AuthGuard이제 만들 는 인증된 사용자(따라서 요청 헤더에 토큰이 첨부됨)를 가정합니다. 토큰을 추출하고 유효성을 검사한 후, 추출된 정보를 사용하여 요청 진행 여부를 결정합니다.


import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

역할 기반 인증

특정 역할을 가진 사용자에게만 접근을 허용하는 더욱 기능적인 가드를 만들어 보겠습니다. 기본 가드 템플릿부터 시작하여 다음 섹션에서 이를 기반으로 확장해 보겠습니다. 지금은 모든 요청이 진행되도록 허용합니다.


import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}

바인딩 가드

파이프 및 예외 필터처럼 가드는 컨트롤러 범위 , 메서드 범위 또는 전역 범위일 수 있습니다. 아래에서는 @UseGuards()데코레이터를 사용하여 컨트롤러 범위 가드를 설정합니다. 이 데코레이터는 단일 인수 또는 쉼표로 구분된 인수 목록을 받을 수 있습니다. 이를 통해 하나의 선언으로 적절한 가드 세트를 쉽게 적용할 수 있습니다.


@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}

@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}


const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}

핸들러당 역할 설정

RolesGuard시스템은 잘 작동하지만, 아직 그다지 똑똑하지는 않습니다. 가장 중요한 가드 기능인 실행 컨텍스트 를 아직 활용하지 못하고 있습니다 . 아직 역할, 즉 각 핸들러에 허용되는 역할에 대해 알지 못합니다.예를 들어 CatsController, 는 경로마다 다른 권한 체계를 가질 수 있습니다. 어떤 경로는 관리자에게만 제공되고, 어떤 경로는 모든 사용자에게 공개될 수 있습니다. 유연하고 재사용 가능한 방식으로 역할과 경로를 어떻게 일치시킬 수 있을까요?

여기서 사용자 지정 메타데이터가 중요한 역할을 합니다. Nest는 정적 메서드를 통해 생성된 데코레이터 또는 기본 제공 데코레이터를 통해 경로 핸들러에 사용자 지정 메타데이터를 첨부할 수 있는 기능을 제공합니다

예를 들어, 메타데이터를 핸들러에 첨부하는 메서드를 @Roles()사용하여 데코레이터를 만들어 보겠습니다 . 이는 프레임워크에서 기본적으로 제공되며 패키지에서 노출됩니다.


import { Reflector } from '@nestjs/core';

export const Roles = Reflector.createDecorator<string[]>();


@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}



import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Roles } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get(Roles, context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

0개의 댓글