NestJS Request Lifecycle (2) - Guard

2022. 12. 23. 19:35ใ†Backend/NestJS

์ด์ „ ๊ธ€


๊ฐ€๋“œ

NestJS ๋‚ด์—์„œ ๊ฐ€๋“œ๋ž€ ์š”์ฒญ ์ƒ๋ช…์ฃผ๊ธฐ์—์„œ ๋ฏธ๋“ค์›จ์–ด์˜ ๋’ค์ชฝ, ์ธํ„ฐ์…‰ํ„ฐ์˜ ์•ž์ชฝ์— ์œ„์น˜ํ•˜์—ฌ ํŠน์ • ๊ฒฝ๋กœ๋กœ์˜ ์š”์ฒญ์„ ์Šน์ธํ• ์ง€ ๋ง์ง€์— ๋Œ€ํ•œ ํŒ๋‹จ์„ ๋‚ด๋ฆฌ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. Express๋กœ ๋กœ์ง์„ ๊ฐœ๋ฐœํ•˜๋‹ค๋ณด๋ฉด ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ๋ฅผ ๋ฏธ๋“ค์›จ์–ด์—์„œ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žฆ์€๋ฐ, NestJS์—์„œ๋Š” ์ด๋ฅผ ๊ฐ€๋“œ๋ผ๋Š” ์ƒ๋ช…์ฃผ๊ธฐ์—์„œ ๋”ฐ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

๋ฏธ๋“ค์›จ์–ด ๋Œ€์‹  ๊ฐ€๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ?

๊ทธ๋ ‡๋‹ค๋ฉด NestJS์—์„œ๋Š” ์™œ ๊ตณ์ด ๊ฐ€๋“œ๋ฅผ ๋”ฐ๋กœ ๋‘๋Š” ๊ฒƒ์ผ๊นŒ? NestJS ๊ณต์‹๋ฌธ์„œ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์–ธ๊ธ‰ํ•˜๊ณ  ์žˆ๋‹ค. (์˜์—ญ ๋‹ค์ˆ˜...)

๋ฏธ๋“ค์›จ์–ด๋Š” ๋ณธ์งˆ์ ์œผ๋กœ ๋˜‘๋˜‘ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๋“ค์›จ์–ด๋Š” next() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ ๋’ค ์–ด๋–ค ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋˜๋Š”์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, ๊ฐ€๋“œ๋Š” ExecutionContext ๊ฐ์ฒด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ดํ›„ ์–ด๋–ค ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋˜๋Š”์ง€ ์ •ํ™•ํžˆ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋“œ๋Š” Pipe, Interceptor, Exception filter์™€ ๋น„์Šทํ•˜๊ฒŒ ๋””์ž์ธ๋˜์–ด, ์š”์ฒญ/๋ฐ˜ํ™˜ ์ฃผ๊ธฐ ๋‚ด ์ ์ ˆํ•œ ์ง€์ ์— ๋” ์„ ์–ธ์ ์œผ๋กœ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๋ก ์ ์œผ๋กœ, ์ฝ”๋“œ๋ฅผ ๋” ์„ ์–ธ์ ์ด๊ณ  DRY ์›์น™์„ ๋”ฐ๋ฅด๋„๋ก ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โ€ป DRY ์›์น™: Do not Repeat Yourself, ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ค‘๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ฐฉ์ง€ ํ•˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ ์›์น™

์ฆ‰, ExecutionContext ๊ฐ์ฒด์˜ ์œ ๋ฌด๋กœ ๊ฐ€๋“œ์™€ ๋ฏธ๋“ค์›จ์–ด์˜ ์ฐจ์ด๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ด๋„ ๋ฌด๋ฐฉํ•œ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด, ์—ฌ๊ธฐ์„œ ์–ธ๊ธ‰๋˜๋Š” ExecutionContext๋Š” ๋ฌด์—‡์ผ๊นŒ??

ExecutionContext (์‹คํ–‰ ์ปจํ…์ŠคํŠธ)

๋„ˆ๋ฌด ๊ธธ๊ฒŒ ์–ธ๊ธ‰ํ•˜๋ฉด ๊ธ€์ด ๋„ˆ๋ฌด ์‚ฐ์œผ๋กœ ๊ฐ€๋ ค๊ณ  ํ•˜๋‹ˆ ๊ฐ„๋žตํ•˜๊ฒŒ ์ด์•ผ๊ธฐํ•˜๋ฉด, NestJS ๋‚ด์—์„œ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ, ๋ฐ˜ํ™˜ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์„ ๋ฟ ์•„๋‹ˆ๋ผ ํ•ด๋‹น ์š”์ฒญ์ด ์–ด๋–ค ์ปจํŠธ๋กค๋Ÿฌ์˜ ์–ด๋–ค ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋˜๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋„ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์ด๋‹ค.

์ด๋Š” ์ง€๊ธˆ ๋‹ค๋ฃจ๋Š” ๊ฐ€๋“œ ๋ฟ ์•„๋‹ˆ๋ผ ํ–ฅํ›„ ๋‹ค๋ฃฐ ์ธํ„ฐ์…‰ํ„ฐ, ์˜ˆ์™ธ ํ•„ํ„ฐ์—์„œ๋„ ๋“ฑ์žฅํ•œ๋‹ค. 

 

๊ฐ€๋“œ ์ž‘์„ฑ

NestJS์—์„œ ๊ฐ€๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” @Injectable() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ด์šฉํ•œ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค. ์ด ๋•Œ ์ƒ์„ฑ๋˜๋Š” ํด๋ž˜์Šค๋Š” NestJS์˜ CanActivate๋ฅผ ์ƒ์†ํ•ด์•ผํ•˜๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” canActivate ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค. ํ•ด๋‹น ๋ฉ”์„œ๋“œ์—์„œ ExecutionContext๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ด์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์„ ํ™•์ธํ•˜๊ณ , ์ตœ์ข…์ ์œผ๋กœ๋Š” boolean ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์š”์ฒญ์„ ๋‚ด๋ถ€ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌํ• ์ง€, ์•„๋‹ˆ๋ฉด 403(Forbidden)์„ ๊ณง๋ฐ”๋กœ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ํ• ์ง€์— ๋Œ€ํ•œ ํŒ๋‹จ์ด ์ด๋ฃจ์–ด์ง„๋‹ค. 

์•„๋ž˜๋Š” ์•ฝ์‹์œผ๋กœ ์ž‘์„ฑํ•œ ๋ชจ๋“  ์š”์ฒญ์„ ํ†ต๊ณผ์‹œํ‚ค๋Š” TestGuard์ด๋‹ค.

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

@Injectable()
export class TestGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true; // ๋ชจ๋“  ์š”์ฒญ์„ ํ†ต๊ณผ์‹œํ‚ค๋Š” ๊ฐ€๋“œ
  }
}

 

๊ฐ€๋“œ ์ ์šฉ

๊ฐ€๋“œ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ 3๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•œ๋‹ค.

  1. ๋ชจ๋“ˆ ๋ฐ–์—์„œ ์ž‘๋™ํ•˜๋Š” ์ „์—ญ ๊ฐ€๋“œ
  2. ๋ชจ๋“ˆ ๋‚ด์—์„œ ์ž‘๋™ํ•˜๋Š” ์ „์—ญ ๊ฐ€๋“œ
  3. ํŠน์ • ์ปจํŠธ๋กค๋Ÿฌ์— ๋Œ€ํ•ด ์ž‘๋™ํ•˜๋Š” ๊ฐ€๋“œ

๋ชจ๋“ˆ ๋ฐ–์—์„œ ์ž‘๋™ํ•˜๋Š” ์ „์—ญ ๊ฐ€๋“œ

ํ•ด๋‹น ๋ฐฉ์‹์€ useGlobalGuards()๋ผ๋Š” NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•œ๋‹ค. ์ด๋Š” ์ „์—ญ ๋ฏธ๋“ค์›จ์–ด์˜ ์ ์šฉ ๋ฐฉ์‹๊ณผ ๋น„์Šทํ•œ๋ฐ, ํ•ด๋‹น ๋ฐฉ์‹์„ ์ด์šฉํ•˜๋ฉด ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์˜์กด์„ฑ์„ ํ™œ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

import { NestFactory } from '@nestjs/core';
import { TestGuard } from './app.guard';
import { AppModule } from './app.module';

async function bootstrap() {
  // NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค
  const app = await NestFactory.create(AppModule);
  app.useGlobalGuards(new TestGuard()); // ํด๋ž˜์Šค๊ฐ€ ์•„๋‹Œ ์ธ์Šคํ„ด์Šค ํ˜•ํƒœ๋กœ ์ธ์ž์— ํฌํ•จ์‹œ์ผœ์•ผ ํ•จ
  await app.listen(3000);
}
bootstrap();

 ์ด ๋•Œ ํ•ด๋‹น ๋ฐฉ์‹์„ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฉ”์„œ๋“œ์— ๊ฐ€๋“œ๋ฅผ ์ธ์Šคํ„ด์Šค ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

๋ชจ๋“ˆ ๋‚ด์—์„œ ์ž‘๋™ํ•˜๋Š” ์ „์—ญ ๊ฐ€๋“œ

์ „์—ญ ๊ฐ€๋“œ๋ฅผ ๋ชจ๋“ˆ ๋ฐ–์—์„œ ์ ์šฉํ•  ๊ฒฝ์šฐ ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ „์—ญ ๊ฐ€๋“œ ์„ค์ •์„ ํ•ด์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AppController } from './app.controller';
import { TestGuard } from './app.guard';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    { // ์ „์—ญ ๊ฐ€๋“œ ์„ค์ •
      provide: APP_GUARD, 
      useClass: TestGuard 
    }, 
    AppService
  ],
})
export class AppModule {}

ํŠน์ • ์ปจํŠธ๋กค๋Ÿฌ์— ๋Œ€ํ•ด ์ž‘๋™ํ•˜๋Š” ๊ฐ€๋“œ

@UseGuards() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํŠน์ • ์ปจํŠธ๋กค๋Ÿฌ์—๋งŒ ๊ฐ€๋“œ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { TestGuard } from './app.guard';
import { AppService } from './app.service';

@Controller()
@UseGuards(TestGuard)
// @UseGuards(new TestGuard())๋„ ๊ฐ€๋Šฅ
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('test')
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('test2')
  getTest(): string {
    return this.appService.getBye();
  }
}

@UseGuards()์˜ ์ธ์ž๋กœ๋Š” Guard ํด๋ž˜์Šค๊ฐ€ ์˜ฌ ์ˆ˜๋„, ํ˜น์€ ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ƒ์„ฑ๋œ ์ธ์Šคํ„ด์Šค๊ฐ€ ์˜ฌ ์ˆ˜๋„ ์žˆ๋‹ค.

๋˜ํ•œ ์ปจํŠธ๋กค๋Ÿฌ ์ „์ฒด๊ฐ€ ์•„๋‹Œ ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด ํŠน์ • ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด์„œ๋งŒ ์ ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { TestGuard } from './app.guard';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('test')
  @UseGuards(TestGuard) // test ๊ฒฝ๋กœ์— ๋Œ€ํ•ด์„œ๋งŒ ๊ฐ€๋“œ ์ ์šฉ
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('test2')
  getTest(): string {
    return this.appService.getBye();
  }
}

์ด๋ฅผ ์ด์šฉํ•˜๋ฉด ์šฐ์„  ์ปจํŠธ๋กค๋Ÿฌ ์ „์ฒด์— ํŠน์ • ๊ฐ€๋“œ๋ฅผ ์ ์šฉ์‹œํ‚ค๊ณ , ํŠน์ • ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด์„œ๋Š” ๋”ฐ๋กœ ๋‹ค๋ฅธ ๊ฐ€๋“œ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ์‹ (override)๋กœ๋„ ํ™œ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. 

์—ฌ๊ธฐ๊นŒ์ง€ ๋”ฐ๋ผ์™”๋‹ค๋ฉด NestJS ํ”„๋กœ์ ํŠธ์— ๊ฐ€๋“œ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์•„์ง์€ ๋ฐ˜์ชฝ์งœ๋ฆฌ์— ๋ถˆ๊ณผํ•˜๋‹ค. ์™œ๋ƒํ•˜๋ฉด ExecutionContext๋ฅผ ์•„์ง ํ™œ์šฉํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค! 

 

๊ฐ€๋“œ์—์„œ ExecutionContext ํ™œ์šฉ

์•ž์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, NestJS์—์„œ ๊ฐ€๋“œ์™€ ๋ฏธ๋“ค์›จ์–ด์˜ ์ฐจ์ด๋Š” ExecutionContext์˜ ์œ ๋ฌด๋กœ ๊ฐˆ๋ฆฌ๋ฉฐ ์ด๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ? 

์šฐ์„ , ExecutionContext์˜ getClass()์™€ getHandler() ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ํ•ด๋‹น ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋  ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์™€ ๋ฉ”์„œ๋“œ์— ์ง€๋‚˜์ง€ ์•Š๋Š”๋‹ค. ์ด๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์ง์ ‘ ํ•ธ๋“ค๋Ÿฌ์— ํŠน์ • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(๊ฐ€๋ น, ํ•ธ๋“ค๋Ÿฌ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ)๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ๊ฐ€๋“œ์—์„œ๋Š” ExecutionContext๋ฅผ ์ด์šฉํ•˜์—ฌ ์š”์ฒญํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ์˜ ๊ถŒํ•œ์„ ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ๊ณผ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ๋„์™€์ฃผ๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ NestJS์˜  SetMetadata์ด๋‹ค. 

SetMetadata

NestJS์˜ SetMetadata๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด๋ฅผ ์ง์ ‘ ์ด์šฉํ•˜์—ฌ ๋ฐ์ฝ”๋ ˆ์ดํŒ…ํ•˜๋Š” ๋ฐฉ์‹์€ ๊ถŒ์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•œ๋‹ค. ๋Œ€์‹  ์ด๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•˜๊ณ  ์žˆ๋‹ค.

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

// SetMetadata(key, value)
// ํ•ธ๋“ค๋Ÿฌ์— ์ถ”๊ฐ€ํ•  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ‚ค, ๋ฐธ๋ฅ˜ ํ˜•์‹์œผ๋กœ ๋ถ€์—ฌ
// ๊ฐ ํ•ธ๋“ค๋Ÿฌ์˜ ์ ‘๊ทผ ๊ถŒํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์ƒ์„ฑ
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

์œ„ ์ฝ”๋“œ๋Š” ํ•ธ๋“ค๋Ÿฌ์— 'roles'๋ผ๋Š” ํ‚ค๋ฅผ ์ด์šฉํ•˜์—ฌ ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์„ค์ •ํ•ด์ฃผ๋Š” ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์„ ์–ธํ•ด์ค€ ๊ฒƒ์ด๋‹ค.

์ด์ œ ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ์œ„ ์˜ˆ์‹œ์˜ getHello() ํ•ธ๋“ค๋Ÿฌ์— 'admin'(๊ด€๋ฆฌ์ž) ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ฒ ๋‹ค.

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { SetMetadata } from '@nestjs/common';

// SetMetadata(key, value)
// ํ•ธ๋“ค๋Ÿฌ์— ์ถ”๊ฐ€ํ•  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ‚ค, ๋ฐธ๋ฅ˜ ํ˜•์‹์œผ๋กœ ๋ถ€์—ฌ
// ๊ฐ ํ•ธ๋“ค๋Ÿฌ์˜ ์ ‘๊ทผ ๊ถŒํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์ƒ์„ฑ
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);


@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('test')
  @Roles('admin') // getHello() ํ•ธ๋“ค๋Ÿฌ์— 'roles' -> 'admin' ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ถ€์—ฌ
  getHello(): string {
    return this.appService.getHello();
  }
}

์œ„์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด์ฃผ๋ฉด ํ•ธ๋“ค๋Ÿฌ์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋กœ ์ถ”๊ฐ€ํ•ด์ค€ ๊ฒƒ์ด๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•  ์ผ์€, ์ด์ œ ๊ฐ€๋“œ์—์„œ ์„ค์ •ํ•ด ์ค€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ , ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์— ์ด์— ๋ถ€ํ•ฉํ•˜๋Š” ์ธ์ฆ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์ฃผ๋ฉด ๋œ๋‹ค!

Reflector๋ฅผ ์ด์šฉํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ ‘๊ทผ

์ถ”๊ฐ€ํ•ด๋‘” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” NestJS์—์„œ ์ œ๊ณต๋˜๋Š” Reflector๋ฅผ ๊ฐ€๋“œ์— ์˜์กด์„ฑ ์ฃผ์ž…ํ•˜๊ณ  ์ด๋ฅผ ํ™œ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํƒ€๊นƒ ํ•ธ๋“ค๋Ÿฌ์˜ 'roles'->'admin' ์ด๋ผ๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„, request์— ํฌํ•จ๋œ ์œ ์ €์˜ ๊ถŒํ•œ์ด ์ด์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค. ์ตœ์ข…์ ์ธ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

// app.guard
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';

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

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const roles = this.reflector.get<string[]>('roles',context.getHandler());
    
    // ๋งŒ์•ฝ ๊ถŒํ•œ ์š”๊ตฌ๊ฐ€ ๋”ฐ๋กœ ์—†๋‹ค๋ฉด ๊ณง๋ฐ”๋กœ ๊ฐ€๋“œ ํ†ต๊ณผ
    if (!roles) {
        return true;
    }

    const request = context.switchToHttp().getRequest();
    const userRole = request.user.role;

    // ๋งŒ์•ฝ ์œ ์ €์˜ ๊ถŒํ•œ์ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ํฌํ•จ๋œ๋‹ค๋ฉด true, ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด false
    return roles.includes(userRole);
  }
}
// app.controller
import { Controller, Get, UseGuards } from '@nestjs/common';
import { TestGuard } from './app.guard';
import { AppService } from './app.service';
import { SetMetadata } from '@nestjs/common';

// SetMetadata(key, value)
// ํ•ธ๋“ค๋Ÿฌ์— ์ถ”๊ฐ€ํ•  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ‚ค, ๋ฐธ๋ฅ˜ ํ˜•์‹์œผ๋กœ ๋ถ€์—ฌ
// ๊ฐ ํ•ธ๋“ค๋Ÿฌ์˜ ์ ‘๊ทผ ๊ถŒํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์ƒ์„ฑ
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);


@Controller()
@UseGuards(TestGuard)
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('test')
  @Roles('admin')
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('test2')
  getTest(): string {
    return this.appService.getBye();
  }
}

๊ฐ€๋“œ ๋‚ด ๋กœ์ง์˜ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ํƒ€๊นƒ ํ•ธ๋“ค๋Ÿฌ ๋‚ด ๊ถŒํ•œ ์š”์ฒญ์ด ์—†๋‹ค๋ฉด ๋ฐ”๋กœ ํ†ต๊ณผ
  2. ๊ถŒํ•œ ์š”์ฒญ์ด ์กด์žฌํ•  ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ ํ™•์ธ
  3. ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ๋ถ€ํ•ฉํ•˜๋Š” ๊ฒฝ์šฐ true, ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด false ๋ฐ˜ํ™˜

'Backend > NestJS' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

NestJS Request Lifecycle (5) - Exception Filter  (0) 2023.01.13
NestJS Request Lifecycle (4) - Pipe  (0) 2022.12.29
NestJS Request Lifecycle (3) - Interceptor  (0) 2022.12.26
NestJS Request Lifecycle (1) - Middleware  (0) 2022.12.22
NestJS Request Lifecycle (0) - ๊ฐœ์š”  (0) 2022.12.22