Nest.js를 배워보자/6. NestJS Middleware , Guard , Intercept

Guard (가드): 인증/인가 처리

_Blue_Sky_ 2025. 12. 2. 18:31
728x90

 


1. ⚔️ Guard란 무엇이며 왜 필요한가요?

가드(Guard)는 특정 라우트(Route)에 접근하는 클라이언트에게 권한이 있는지 확인하여 요청 처리를 허용하거나 거부하는 역할을 합니다. NestJS 파이프라인에서 미들웨어 다음으로 실행되며, Guard가 통과(모두 true 반환)되어야만 요청이 컨트롤러 핸들러로 전달됩니다.

Guard의 주요 역할

  • 인증 (Authentication): 요청을 보낸 사용자가 시스템에 로그인된 유효한 사용자인지 확인합니다 (예: JWT 토큰 검증).
  • 인가 (Authorization): 로그인된 사용자가 해당 리소스에 접근하거나 특정 작업을 수행할 권한(Role)이 있는지 확인합니다 (예: "관리자만 사용자 삭제 가능").

2. 🏗️ Guard 구현 방법

Guard는 CanActivate 인터페이스를 구현하며, canActivate() 메서드가 핵심 로직을 담당합니다.

728x90

2.1. CanActivate 인터페이스

Guard 클래스는 @Injectable() 데코레이터를 사용하고 CanActivate를 구현합니다. 

// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service'; // DI를 위해 서비스 주입

@Injectable()
export class AuthGuard implements CanActivate {
  // 💡 DI 컨테이너에서 AuthService를 주입받을 수 있습니다.
  constructor(private authService: AuthService) {}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    
    // 1. 요청 컨텍스트 가져오기 (HTTP, WebSockets 등)
    const request = context.switchToHttp().getRequest();
    
    // 2. 인증 로직 수행 (예: 요청 헤더에서 JWT 추출 및 검증)
    const isAuthenticated = this.validateRequest(request); 

    // 3. 💡 최종적으로 boolean 값을 반환
    // true: 요청 처리 계속 진행
    // false: 요청 처리 중단 및 HTTP 403 Forbidden 반환
    return isAuthenticated;
  }
  
  private validateRequest(request): boolean {
    // 실제 AuthService의 validateToken 로직을 사용
    const token = request.headers.authorization;
    return this.authService.validateToken(token);
  }
}

2.2. ExecutionContext의 중요성

canActivate() 메서드는 ExecutionContext 객체를 인자로 받습니다. 이 객체는 현재 실행 중인 핸들러(라우트 메서드)에 대한 정보를 담고 있으며, HTTP 외의 다른 컨텍스트(WebSockets, RPC 등)에서도 Guard를 사용할 수 있도록 일반화된 API를 제공합니다.

  • context.switchToHttp().getRequest(): 현재 HTTP 요청 객체(req)를 가져옵니다.

3. 📌 Guard 적용 및 실행 순서

Guard는 컨트롤러, 컨트롤러 메서드, 또는 글로벌 레벨에서 @UseGuards() 데코레이터를 사용하여 적용합니다.

3.1. 실행 순서

요청이 들어왔을 때 Guard는 미들웨어 다음, 인터셉터와 파이프보다 먼저 실행됩니다.

  1. Middleware
  2. Guard (인증/인가)
  3. Pipe (데이터 검증/변환)
  4. Interceptor (요청 전 처리)
  5. Controller Handle

      
728x90

3.2. 적용 예시

// users.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { RolesGuard } from './roles.guard'; // 인가 Guard

@Controller('users')
// 💡 1. 클래스 레벨 적용: 이 컨트롤러의 모든 라우트에 적용됩니다.
@UseGuards(AuthGuard, RolesGuard)
export class UsersController {
  
  @Get() // GET /users
  findAll() {
    // AuthGuard와 RolesGuard를 통과한 요청만 여기에 도달
  }

  @Get('public')
  // 💡 2. 메서드 레벨 적용: 특정 Guard를 무시하거나 다른 Guard를 추가할 수 있습니다.
  @UseGuards() // 비어있는 @UseGuards()로 클래스 레벨 Guard를 오버라이드할 수 있습니다.
  findPublic() {
    return 'Public access allowed';
  }
}

4. 🥇 Guard의 장점: DI 기반의 강력한 인증/인가

Guard의 가장 큰 장점은 NestJS의 DI 시스템 내에서 작동한다는 것입니다.

  • Guard는 생성자를 통해 AuthService, UsersService 등 복잡한 비즈니스 로직을 가진 서비스를 안전하게 주입받아 사용할 수 있습니다.
  • 이는 인증/인가 로직을 재사용 가능한 서비스에 분리하여, 테스트하기 쉽고 유지보수가 용이한 느슨하게 결합된 코드를 만들 수 있게 합니다.

미들웨어가 Express의 낮은 수준의 요청 전처리 도구라면, Guard는 NestJS의 아키텍처 원칙(DI)에 부합하는 권한 처리의 표준 방법입니다.

728x90