ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Nest.js] Guards
    Backend/Nest.js 2024. 4. 4. 23:49
    728x90

    Guards

    애플리케이션의 최전선에서 말그대로 애플리케이션을 보호하는 역할을 담당합니다.

    가드를 이용하면 컨트롤러가 요청을 처리하기 전에 안전하지 않은 요청을 효과적으로 차단할 수 있습니다. 따라서 애플리케이션 보안을 위해서 필수적인 사용자 인증이나 접근 제어를 구현하는 데에 유용합니다.

    NestJS로 들어오는 요청은 컨트롤러(controller) 단에 도달하기 전에 반드시 가드를 거쳐가도록 되어 있습니다.

    기본적으로, Guard는 @Injectable() 데코레이터로 주석이 달린 클래스로, CanActivate 인터페이스를 구현합니다.

    단일 책임이 존재하고, 특정조건에 따라 요청을 라우터 핸들러에 의해 처리할지 여부 결정합니다.

    ⇒ 승인(Authorization)

    특정 조건 : 권한, 역할, ACL(Access Control List) 등

    더보기

    ACL : 트래픽 필터링과 방화벽을 구축하는데 가장 중요한 요소, 허가되지 않은 이용자가 라우터나 네트워크의 특정 자원을 접근하려고 하는 것을 차단

     

    Guards는 NestJS 엔드포인트에 대한 접근을 허용하거나 거부하는 역할을 합니다. 예를 들어, 사용자 프로필을 업데이트하는 라우트를 보호하기 위해 Guard를 생성하고, 이를 관리자만 접근할 수 있도록 설정할 수 있습니다.

    NestJS Guards는 미들웨어와 유사합니다. 둘 다 애플리케이션의 라우트를 보호하며, 인증, 권한 부여 및 속도 제한과 같은 다양한 보안 조치를 추가합니다.

    middleware next() function이 호출 후에 어떤 handler가 실행될 지를 알 수 없습니다.

    그러나 Guards 는 ExecutionContext instacne에 접근이 가능하므로 다음에 실행될 작업을 알고 있습니다.

    💡 Guards 는 모든 middleware의 다음에 실행되고, interceptor 나 pipe 이전에 실행됩니다. 즉, middleware → guards → interceptor or pipe 순서입니다.

     

    모든 guard는 canActivate() 함수를 implement해야 합니다. 이 함수는 현재의 request가 실행될 수 있는지 없는지를 나타내는 boolean을 리턴해야 합니다. true라면 해당 request는 실행될 것이고, false라면 거절됩니다.

     

    CanActive 인터페이스

    CanActive 인터페이스의 구조는 아래와 같습니다.

    export interface CanActivate {
      canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>;
    }
    

    canActivate라는 메소드가 있으며 파라미터로 실행 컨텍스트(ExecutionContext)를 받고 있습니다.

     

    실행 컨텍스트(ExecutionContext)

    canActivate 메소드의 파라미터로 받는 실행 컨텍스트는 ArgumentHost를 상속받고 있습니다.

    export interface ArgumentsHost {
      getArgs<T extends Array<any> = any[]>(): T;
      getArgByIndex<T = any>(index: number): T;
      switchToRpc(): RpcArgumentsHost;
      switchToHttp(): HttpArgumentsHost;
      switchToWs(): WsArgumentsHost;
      getType<TContext extends string = ContextType>(): TContext;
    }
    
    export interface ExecutionContext extends ArgumentsHost {
      getClass<T = any>(): Type<T>;
      getHandler(): Function;
    }
    

    ArgumentsHost 인터페이스는 다양한 타입의 요청(예: HTTP, WebSocket, RPC)을 처리할 때 필요한 맥락(context) 정보를 제공하는 역할을 합니다. 각 요청 타입에 따라 다른 종류의 데이터가 필요할 수 있으며, ArgumentsHost는 이러한 데이터에 접근하기 위한 일관된 인터페이스를 제공합니다.

    • 주요 메서드
      1. getArgs<T = any[]>(): T: 현재 핸들러에 전달된 인자들을 배열로 반환합니다. 이 메서드를 통해 요청의 모든 인자에 접근할 수 있습니다.
      2. getArgByIndex<T = any>(index: number): T: 특정 인덱스의 인자를 반환합니다. 이를 통해 특정 위치의 인자에 직접 접근할 수 있습니다.
      3. switchToHttp(): HttpArgumentsHost: 현재 요청이 HTTP 요청인 경우에 관련된 HttpArgumentsHost 객체를 반환합니다. 이 객체는 HTTP 요청과 응답 객체에 대한 접근을 제공합니다.
      4. switchToWs(): WsArgumentsHost: WebSocket 요청을 처리할 때 사용됩니다. 이 메서드는 WebSocket 연결과 관련된 데이터에 접근할 수 있는 WsArgumentsHost 객체를 반환합니다.
      5. switchToRpc(): RpcArgumentsHost: RPC(원격 프로시저 호출) 요청을 처리할 때 사용됩니다. 이 메서드는 RPC 요청과 관련된 데이터에 접근할 수 있는 RpcArgumentsHost 객체를 반환합니다.
      6. getType<TContext = 'http' | 'ws' | 'rpc'>(): TContext: 현재 요청의 타입을 반환합니다. 이 메서드는 요청이 HTTP, WebSocket, 또는 RPC 중 어느 것인지 판단하는 데 사용될 수 있습니다.

     

    ExecutionContext는 ArgumentsHost를 상속 받았기 때문에 각 통신 프로토콜에 맞는 switch 메소드를 통해 Request, Response, next()를 얻어올 수 있습니다.

    여기서 중요하게 보아야 할 점은 ExecutionContext가 가지고 있는 method입니다.

    getClass는 클라이언트로 요청이 들어왔을 때 처리할 수 있는 라우트 핸들러를 가진 컨트롤러에 대한 정보를 가지고 있으며, getHandler는 클라이언트로 들어온 요청을 처리하는 라우트 핸들러에 대한 정보를 가지고 있습니다.

    그렇기 때문에 미들웨어와 달리 이 후 실행되는 컨트롤러나 라우트 핸들러에 대한 정보를 알 수 있습니다.

     

    Authorization Guard

    • 승인: 충분한 권한이 있는 호출자만 특정 라우터 사용 가능
    • 모든 가드는 canActivate() 함수 구현해야 함
      • 단일 인수로 ExecutionContext 인스턴스를 받음
      • 현재 요청 허용 여부를 boolean값으로 반환

    토큰을 추출한 후 확인, 추출한 정보로부터 해당 request 가 실행될 수 있을 지 없을지 판단

    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);
      }
    }
    

     

    Role-based authentication

    • 특정 역할을 가진 사용자에게만 엑세스를 허용하는 (보다 기능적인)가드
    @Injectable()
    export class RolesGuard implements CanActivate {
      canActivate(
        context: ExecutionContext,
      ): boolean | Promise<boolean> | Observable<boolean> {
        // 가드 내용
        return true;
      }
    }
    

     

    Binding guards

    • 전역 Guard

    Nest 애플리케이션 인스턴스의 useGlobalGuards() 메소드를 사용합니다.

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

     

    • 단일 메서드 Guard

    @UseGuards() 데코레이터는 Guard를 컨트롤러 또는 메소드의 범위에 바인딩합니다.

    @UseGuards(AccessTokenInCookieAuthGuard) // 엑세스 토큰을 쿠키에서 가져와 사용자 인증을 하는 커스텀 guard
    @Post()
    async controllerFunciton(): Promise<ResponseType> {
      return this.Service.serviceFunction()
    }
    

     

    • 컨트롤러 범위 Guard

    컨트롤러 레벨에서 @UseGuards를 사용하면, 해당 컨트롤러의 모든 핸들러(메소드)에 Guard가 적용됩니다.

    @UseGuards(AuthGuard)
    @Controller()
    export class AppController {
      constructor(private readonly appService: AppService) {}
    
      @Get()
      getHello(): string {
        return this.appService.getHello();
      }
    }
    

    이러한 방식으로, NestJS에서 Guards는 애플리케이션의 보안과 접근 제어를 강화하는 데 핵심적인 역할을 합니다.

     


     

    REF

    https://velog.io/@junguksim/NestJS-노트-2-Guards

    https://velog.io/@hahaha/NestJS-Guards

    https://velog.io/@dev_leewoooo/NestJs-Guard

    728x90

    'Backend > Nest.js' 카테고리의 다른 글

    [Nest.js] Test Code  (0) 2023.11.25
    [Nest.js] TypeORM, Repository Pattern  (0) 2023.10.02
    Nest.js  (0) 2023.09.18
Designed by Tistory.