NestJS 가드(Guards) 가이드

가드는 NestJS에서 특정 라우트 핸들러가 요청을 처리해야 하는지 여부를 결정하는 역할을 합니다. 주로 인증과 권한 부여(인가)에 사용되며, 미들웨어와 달리 실행 컨텍스트에 접근할 수 있어 더 정교한 제어가 가능합니다.

가드의 주요 특징

  • @Injectable() 데코레이터를 사용하여 정의
  • CanActivate 인터페이스 구현 필요
  • 요청 처리 여부를 결정하는 boolean 값 반환
  • 미들웨어 다음, 인터셉터와 파이프 이전에 실행

가드 구현 방법

모든 가드는 CanActivate 인터페이스를 구현해야 하며, canActivate() 메서드를 제공해야 합니다:

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 this.validateRequest(request);
  }
 
  private validateRequest(request: any): boolean {
    // 요청의 유효성을 검증하는 로직
    // 예: JWT 토큰 확인, 사용자 인증 상태 확인 등
    return true; // 또는 false
  }
}

가드 적용 방법

컨트롤러 레벨 적용

import { Controller, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
 
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {
  // ...
}

메서드 레벨 적용

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
 
@Controller('cats')
export class CatsController {
  @Get()
  @UseGuards(AuthGuard)
  findAll() {
    return this.catsService.findAll();
  }
}

전역 가드 적용

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AuthGuard } from './auth.guard';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalGuards(new AuthGuard());
  await app.listen(3000);
}
bootstrap();

의존성 주입을 통한 전역 가드 적용

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
 
@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AppModule {}

실행 컨텍스트 활용

가드의 핵심 기능 중 하나는 ExecutionContext 인스턴스에 접근할 수 있다는 것입니다. 이를 통해 현재 실행 중인 핸들러와 그 클래스에 대한 정보를 얻을 수 있습니다:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
 
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
 
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!requiredRoles) {
      return true;
    }
    
    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

메타데이터와 가드 결합

@SetMetadata() 데코레이터와 커스텀 데코레이터를 사용하여 가드에 메타데이터를 제공할 수 있습니다:

// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
 
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// cats.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RolesGuard } from './roles.guard';
import { Roles } from './roles.decorator';
 
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {
  @Get()
  @Roles('admin')
  findAll() {
    return this.catsService.findAll();
  }
}

비동기 가드

가드는 비동기 작업도 지원합니다:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService) {}
 
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);
    
    if (!token) {
      return false;
    }
    
    try {
      const user = await this.authService.validateToken(token);
      request.user = user;
      return true;
    } catch (error) {
      return false;
    }
  }
 
  private extractTokenFromHeader(request: any): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

예외 처리

가드에서 false를 반환하면 NestJS는 자동으로 ForbiddenException을 발생시킵니다. 더 구체적인 예외를 발생시키려면 다음과 같이 작성할 수 있습니다:

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
 
@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const isAuthenticated = this.validateRequest(request);
    
    if (!isAuthenticated) {
      throw new UnauthorizedException('인증이 필요합니다');
    }
    
    return true;
  }
 
  private validateRequest(request: any): boolean {
    // 인증 로직
    return false;
  }
}

실제 활용 사례

  • JWT 인증 가드
  • 역할 기반 접근 제어(RBAC)
  • API 키 검증
  • 요율 제한(Rate limiting)
  • IP 기반 접근 제어

가드는 NestJS에서 인증과 권한 부여를 처리하는 강력한 방법을 제공하며, 애플리케이션의 보안을 강화하는 데 중요한 역할을 합니다.