Guards

Guards

κ°€λ“œλŠ” @Injectable() λ°μ½”λ ˆμ΄ν„°λ‘œ 주석이 달린 ν΄λž˜μŠ€μž…λ‹ˆλ‹€. κ°€λ“œλŠ”CanActivate μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•΄μ•Όν•©λ‹ˆλ‹€.

κ°€λ“œλŠ” 단독 μ±…μž„μ΄ μžˆμŠ΅λ‹ˆλ‹€. λŸ°νƒ€μž„μ— μ‘΄μž¬ν•˜λŠ” νŠΉμ • 쑰건 (예: κΆŒν•œ, μ—­ν• , ACL λ“±)에 따라 μ§€μ •λœ μš”μ²­μ΄ 라우트 ν•Έλ“€λŸ¬μ— μ˜ν•΄ 처리될 μ§€ μ—¬λΆ€λ₯Ό κ²°μ •ν•©λ‹ˆλ‹€. 이λ₯Ό μ’…μ’… κΆŒν•œμ΄λΌκ³ ν•©λ‹ˆλ‹€. 인증 (및 일반적으둜 곡동 μž‘μ—… 인 인증)은 일반적으둜 κΈ°μ‘΄ Express μ‘μš© ν”„λ‘œκ·Έλž¨μ˜ 미듀웨어에 μ˜ν•΄ μ²˜λ¦¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 토큰 μœ νš¨μ„± 검사 및 속성을 μš”μ²­(Request)κ°œμ²΄μ— μ—°κ²°ν•˜λŠ” 것과 같은 것은 νŠΉμ • 경둜 μ»¨ν…μŠ€νŠΈ (및 ν•΄λ‹Ή 메타 데이터)와 κ°•λ ₯ν•˜κ²Œ μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠκΈ° λ•Œλ¬Έμ— λ―Έλ“€μ›¨μ–΄λŠ” 인증에 μ ν•©ν•œ μ„ νƒμž…λ‹ˆλ‹€.

κ·ΈλŸ¬λ‚˜ λ―Έλ“€μ›¨μ–΄λŠ” 본질적으둜 λ°”λ³΄μž…λ‹ˆλ‹€. next()ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œ ν›„ μ–΄λ–€ ν•Έλ“€λŸ¬κ°€ 싀행될지 μ•Œ 수 μ—†μŠ΅λ‹ˆλ‹€. λ°˜λ©΄μ— GuardsλŠ” ExecutionContext μΈμŠ€ν„΄μŠ€μ— μ•‘μ„ΈμŠ€ν•  수 μžˆμœΌλ―€λ‘œ λ‹€μŒμ— 무엇이 μ‹€ν–‰ 될지 μ •ν™•νžˆ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. μš”μ²­/응닡 주기의 μ •ν™•ν•œ μ‹œμ μ— 처리 λ‘œμ§μ„ μ‚½μž…ν•˜κ³  μ„ μ–Έμ μœΌλ‘œ μ²˜λ¦¬ν•  수 μžˆλ„λ‘ μ˜ˆμ™Έ ν•„ν„°, νŒŒμ΄ν”„ 및 인터셉터와 같이 μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ μ½”λ“œλ₯Ό κ±΄μ‘°ν•˜κ³  μ„ μ–Έμ μœΌλ‘œ μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

info 힌트 κ°€λ“œλŠ” 각 미듀웨어 후에 μ‹€ν–‰λ˜μ§€λ§Œ, 인터셉터 λ‚˜ νŒŒμ΄ν”„λŠ” 전에 μ‹€ν–‰λ©λ‹ˆλ‹€.

Authorization guard

μ–ΈκΈ‰ ν•œ 바와 같이 κΆŒν•œμ€ λ°œμ‹ μž (일반적으둜 νŠΉμ • 인증된 μ‚¬μš©μž)μ—κ²Œ μΆ©λΆ„ν•œ κΆŒν•œμ΄ μžˆλŠ” κ²½μš°μ—λ§Œ νŠΉμ • 경둜λ₯Ό μ‚¬μš©ν•  수 μžˆμ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— Guards의 ν›Œλ₯­ν•œ μ‚¬μš© μ‚¬λ‘€μž…λ‹ˆλ‹€. μš°λ¦¬κ°€ ꡬ좕 ν•  AuthGuardλŠ” 이제 인증된 μ‚¬μš©μžλ₯Ό κ°€μ •ν•©λ‹ˆλ‹€ (λ”°λΌμ„œ 토큰이 μš”μ²­ 헀더에 μ²¨λΆ€λ˜μ–΄ 있음). 토큰을 μΆ”μΆœν•˜κ³  μœ νš¨μ„±μ„ κ²€μ‚¬ν•˜κ³  μΆ”μΆœλœ 정보λ₯Ό μ‚¬μš©ν•˜μ—¬ μš”μ²­μ„ μ§„ν–‰ν•  수 μžˆλŠ”μ§€ μ—¬λΆ€λ₯Ό κ²°μ •ν•©λ‹ˆλ‹€.

@@filename(auth.guard)
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);
  }
}
@@switch
import { Injectable } from '@nestjs/common';

@Injectable()
export class AuthGuard {
  async canActivate(context) {
    const request = context.switchToHttp().getRequest();
    return await validateRequest(request);
  }
}

validateRequest() ν•¨μˆ˜ λ‚΄λΆ€μ˜ λ…Όλ¦¬λŠ” ν•„μš”ν•œ 만큼 κ°„λ‹¨ν•˜κ±°λ‚˜ 정ꡐ할 수 μžˆμŠ΅λ‹ˆλ‹€. 이 예의 μš”μ μ€ λ³΄ν˜Έμžκ°€ μš”μ²­/응닡 주기에 μ–΄λ–»κ²Œ λ§žλŠ”μ§€ λ³΄μ—¬μ€λ‹ˆλ‹€.

λͺ¨λ“  κ°€λ“œλŠ” canActivate()ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•΄μ•Όν•©λ‹ˆλ‹€. 이 ν•¨μˆ˜λŠ” ν˜„μž¬ μš”μ²­μ΄ ν—ˆμš©λ˜λŠ”μ§€ μ—¬λΆ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” 쑰건값을 λ°˜ν™˜ν•΄μ•Ό ν•©λ‹ˆλ‹€. 응닡을 동기식 λ˜λŠ” λΉ„λ™κΈ°μ‹μœΌλ‘œ λ°˜ν™˜ ν•  수 μžˆμŠ΅λ‹ˆλ‹€ (Promise λ˜λŠ” Observable을 톡해). NestλŠ” 리턴 값을 μ‚¬μš©ν•˜μ—¬ λ‹€μŒ 쑰치λ₯Ό μ œμ–΄ν•©λ‹ˆλ‹€.

  • trueλ₯Ό λ°˜ν™˜ν•˜λ©΄ μš”μ²­μ΄ μ²˜λ¦¬λ©λ‹ˆλ‹€.

  • 'false'λ₯Ό λ°˜ν™˜ν•˜λ©΄ NestλŠ” μš”μ²­μ„ κ±°λΆ€ν•©λ‹ˆλ‹€.

canActivate()ν•¨μˆ˜λŠ” ExecutionContextμΈμŠ€ν„΄μŠ€λΌλŠ” 단일 인수λ₯Ό μ·¨ν•©λ‹ˆλ‹€. ExecutionContextλŠ” ArgumentsHostμ—μ„œ μƒμ†λ°›μŠ΅λ‹ˆλ‹€. μ˜ˆμ™Έ ν•„ν„° μ±•ν„°μ—μ„œ μ•žμ„œ ArgumentsHostλ₯Ό λ³΄μ•˜μŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” original ν•Έλ“€λŸ¬λ‘œ μ „λ‹¬λœ 인수λ₯Ό κ°μ‹ΈλŠ” 래퍼이며 μ‘μš© ν”„λ‘œκ·Έλž¨μ˜ μœ ν˜•μ— 따라 λ‹€λ₯Έ 인수 배열을 ν¬ν•¨ν•©λ‹ˆλ‹€. 이 μ£Όμ œμ— λŒ€ν•œ μžμ„Έν•œ λ‚΄μš©μ€ μ˜ˆμ™Έ ν•„ν„° 챕터을 λ‹€μ‹œ μ°Έμ‘°ν•˜μ‹­μ‹œμ˜€.

Execution context

ExecutionContextλŠ” ArgumentsHostλ₯Ό ν™•μž₯ν•˜μ—¬ ν˜„μž¬ μ‹€ν–‰ ν”„λ‘œμ„ΈμŠ€μ— λŒ€ν•œ μΆ”κ°€ 정보λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. κ·Έ λͺ¨μŠ΅μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}

getHandler()λ©”μ†Œλ“œλŠ” 호좜될 ν•Έλ“€λŸ¬μ— λŒ€ν•œ μ°Έμ‘°λ₯Ό λ¦¬ν„΄ν•©λ‹ˆλ‹€. getClass()λ©”μ†Œλ“œλŠ” 이 νŠΉμ • ν•Έλ“€λŸ¬κ°€ μ†ν•˜λŠ” Controller 클래슀의 μœ ν˜•μ„ λ¦¬ν„΄ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, ν˜„μž¬ 처리 된 μš”μ²­μ΄ CatsControllerμ—μ„œ create()λ©”μ†Œλ“œλ‘œ μ§€μ •λœ POST μš”μ²­ 인 경우, getHandler()λŠ” create() λ©”μ†Œλ“œμ— λŒ€ν•œ μ°Έμ‘°λ₯Ό λ¦¬ν„΄ν•˜κ³  getClass()λŠ” CatsController type (μΈμŠ€ν„΄μŠ€ μ•„λ‹˜)을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

Role-based authentication

νŠΉμ • 역할을 κ°€μ§„ μ‚¬μš©μžλ§Œ μ•‘μ„ΈμŠ€ ν•  수 μžˆλŠ” 보닀 κΈ°λŠ₯적인 보호 κΈ°λŠ₯을 ꡬ좕해 λ³΄κ² μŠ΅λ‹ˆλ‹€. κΈ°λ³Έ κ°€λ“œ ν…œν”Œλ¦ΏμœΌλ‘œ μ‹œμž‘ν•˜μ—¬ λ‹€μŒ μ„Ήμ…˜μ—μ„œ μž‘μ„±ν•©λ‹ˆλ‹€. ν˜„μž¬λ‘œμ„œλŠ” λͺ¨λ“  μš”μ²­μ„ μ§„ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@@filename(roles.guard)
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;
  }
}
@@switch
import { Injectable } from '@nestjs/common';

@Injectable()
export class RolesGuard {
  canActivate(context) {
    return true;
  }
}

Binding guards

νŒŒμ΄ν”„ 및 μ˜ˆμ™Έ 필터와 λ§ˆμ°¬κ°€μ§€λ‘œ κ°€λ“œλŠ” 컨트둀러 λ²”μœ„(controller-scoped), 방법 λ²”μœ„(method-scoped) λ˜λŠ” μ „μ—­ λ²”μœ„(global-scoped) 일 수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜λŠ” @UseGuards()λ°μ½”λ ˆμ΄ν„°λ₯Ό μ‚¬μš©ν•˜μ—¬ 컨트둀러 λ²”μœ„μ˜ λ³΄ν˜ΈλŒ€λ₯Ό μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€. 이 λ°μ½”λ ˆμ΄ν„°λŠ” 단일 인수 λ˜λŠ” μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ 인수 λͺ©λ‘μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 ν•œ 번의 μ„ μ–ΈμœΌλ‘œ μ μ ˆν•œ κ°€λ“œ μ„ΈνŠΈλ₯Ό μ‰½κ²Œ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

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

info 힌트 @UseGuards()λ°μ½”λ ˆμ΄ν„°λŠ” @nestjs/common νŒ¨ν‚€μ§€μ—μ„œ κ°€μ Έμ˜΅λ‹ˆλ‹€.

μœ„μ—μ„œ μš°λ¦¬λŠ” μΈμŠ€ν„΄μŠ€ λŒ€μ‹ μ— λ‘€ κ°€λ“œ (RolesGuard) μœ ν˜•μ„ μ „λ‹¬ν•˜μ—¬ ν”„λ ˆμž„ μ›Œν¬μ— μΈμŠ€ν„΄μŠ€ν™”μ™€ μ˜μ‘΄μ„± μ£Όμž…μ„ κ°€λŠ₯ν•˜κ²Œ ν–ˆμŠ΅λ‹ˆλ‹€. νŒŒμ΄ν”„ 및 μ˜ˆμ™Έ 필터와 λ§ˆμ°¬κ°€μ§€λ‘œ λ‚΄λΆ€ μΈμŠ€ν„΄μŠ€λ₯Ό 전달할 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

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

μœ„μ˜ ꡬ성은이 μ»¨νŠΈλ‘€λŸ¬κ°€ μ„ μ–Έν•œ λͺ¨λ“  ν•Έλ“€λŸ¬μ— κ°€λ“œλ₯Ό μ—°κ²°ν•©λ‹ˆλ‹€. κ°€λ“œκ°€ 단일 λ©”μ†Œλ“œμ—λ§Œ μ μš©λ˜λ„λ‘ ν•˜λ €λ©΄ λ©”μ†Œλ“œ μˆ˜μ€€μ—μ„œ @UseGuards()λ°μ½”λ ˆμ΄ν„°λ₯Ό μ μš©ν•©λ‹ˆλ‹€.

μ „μ—­ κ°€λ“œλ₯Ό μ„€μ •ν•˜λ €λ©΄ Nest μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μΈμŠ€ν„΄μŠ€μ˜ useGlobalGuards()λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•˜μ‹­μ‹œμ˜€.

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

warning μ•Œλ¦Ό ν•˜μ΄λΈŒλ¦¬λ“œ μ•±μ˜ 경우 useGlobalGuards()λ©”μ†Œλ“œλŠ” κ²Œμ΄νŠΈμ›¨μ΄ 및 마이크둜 μ„œλΉ„μŠ€μ— λŒ€ν•œ 보호λ₯Ό μ„€μ •ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. "ν‘œμ€€"(ν•˜μ΄λΈŒλ¦¬λ“œκ°€ μ•„λ‹Œ) 마이크둜 μ„œλΉ„μŠ€ μ•±μ˜ 경우 useGlobalGuards()λŠ” κ°€λ“œλ₯Ό μ „μ—­μœΌλ‘œ λ§ˆμš΄νŠΈν•©λ‹ˆλ‹€.

μ „μ—­ κ°€λ“œλŠ” λͺ¨λ“  μ»¨νŠΈλ‘€λŸ¬μ™€ λͺ¨λ“  경둜 μ²˜λ¦¬κΈ°μ— λŒ€ν•΄ 전체 μ‘μš© ν”„λ‘œκ·Έλž¨μ—μ„œ μ‚¬μš©λ©λ‹ˆλ‹€. μ˜μ‘΄μ„± μ£Όμž…μ˜ κ΄€μ μ—μ„œ, λͺ¨λ“ˆ μ™ΈλΆ€μ—μ„œ λ“±λ‘λœ μ „μ—­ κ°€λ“œ (μœ„μ˜ μ˜ˆμ—μ„œμ™€ 같이 useGlobalGuards()둜)λŠ” μ˜μ‘΄μ„±μ΄ μ£Όμž…λ  수 μ—†μŠ΅λ‹ˆλ‹€. μ΄λŠ” λͺ¨λ“ˆμ˜ μ»¨ν…μŠ€νŠΈ λ°–μ—μ„œ μˆ˜ν–‰λ˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. 이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ‹€μŒ ꡬ성을 μ‚¬μš©ν•˜μ—¬ λͺ¨λ“  λͺ¨λ“ˆμ—μ„œ 직접 κ°€λ“œλ₯Ό μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@@filename(app.module)
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

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

info 힌트 κ°€λ“œμ— λŒ€ν•œ μ˜μ‘΄μ„± μ£Όμž…μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•΄μ΄ μ ‘κ·Ό 방식을 μ‚¬μš©ν•  λ•Œ, 이 ꡬ성이 μ‚¬μš©λ˜λŠ” λͺ¨λ“ˆμ— 관계없이, κ°€λ“œλŠ” μ‹€μ œλ‘œ μ „μ—­μž…λ‹ˆλ‹€. μ–΄λ””μ—μ„œ ν•΄μ•Ό ν•©λ‹ˆκΉŒ? κ°€λ“œ (μœ„ μ˜ˆμ—μ„œRolesGuard)κ°€ μ •μ˜λœ λͺ¨λ“ˆμ„ μ„ νƒν•˜μ‹­μ‹œμ˜€. λ˜ν•œ μ»€μŠ€ν…€ ν”„λ‘œ 바이더 등둝을 λ‹€λ£¨λŠ” μœ μΌν•œ 방법은 useClassκ°€ μ•„λ‹™λ‹ˆλ‹€. 여기에 λŒ€ν•΄ μžμ„Ένžˆ μ•Œμ•„λ³΄μ‹­μ‹œμ˜€.

Reflection

우리의 RolesGuardκ°€ μž‘λ™ν•˜κ³  μžˆμ§€λ§Œ 아직 λ˜‘λ˜‘ν•˜μ§€λŠ” μ•ŠμŠ΅λ‹ˆλ‹€. μš°λ¦¬λŠ” 아직 κ°€μž₯ μ€‘μš”ν•œ 보호 κΈ°λŠ₯인 μ‹€ν–‰ μ»¨ν…μŠ€νŠΈ(execution context)λ₯Ό ν™œμš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 아직 μ—­ν• μ΄λ‚˜ 각 μ²˜λ¦¬κΈ°μ— ν—ˆμš©λ˜λŠ” 역할에 λŒ€ν•΄μ„œλŠ” 아직 μ•Œμ§€ λͺ»ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ CatsControllerλŠ” κ²½λ‘œλ§ˆλ‹€ λ‹€λ₯Έ κΆŒν•œ 체계λ₯Ό κ°€μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. μΌλΆ€λŠ” κ΄€λ¦¬μžλ§Œ μ‚¬μš©ν•  수 있고 λ‹€λ₯Έ μΌλΆ€λŠ” λͺ¨λ“  μ‚¬λžŒμ—κ²Œ 곡개될 수 μžˆμŠ΅λ‹ˆλ‹€. μœ μ—°ν•˜κ³  μž¬μ‚¬μš© κ°€λŠ₯ν•œ λ°©μ‹μœΌλ‘œ μ—­ν• κ³Ό 경둜λ₯Ό μ–΄λ–»κ²Œ μΌμΉ˜μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆκΉŒ?

μ‚¬μš©μž μ§€μ • 메타 데이터가 μ‚¬μš©λ˜λŠ” κ³³μž…λ‹ˆλ‹€. NestλŠ” @SetMetadata()λ°μ½”λ ˆμ΄ν„°λ₯Ό 톡해 ν•Έλ“€λŸ¬λ₯Ό λΌμš°νŠΈν•˜κΈ° μœ„ν•΄ μ»€μŠ€ν…€ 메타 데이터λ₯Ό μ²¨λΆ€ν•˜λŠ” κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€. 이 메타 λ°μ΄ν„°λŠ” 슀마트 κ°€λ“œκ°€ 결정을 λ‚΄λ €μ•Ό ν•˜λŠ” λˆ„λ½λœ μ—­ν•  데이터λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. @SetMetadata()λ₯Ό μ‚¬μš©ν•˜μ—¬ λ΄…μ‹œλ‹€ :

@@filename(cats.controller)
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
@@switch
@Post()
@SetMetadata('roles', ['admin'])
@Bind(Body())
async create(createCatDto) {
  this.catsService.create(createCatDto);
}

info 힌트 @SetMetadata()λ°μ½”λ ˆμ΄ν„°λŠ”@nestjs/common νŒ¨ν‚€μ§€μ—μ„œ κ°€μ Έμ˜΅λ‹ˆλ‹€.

μœ„μ˜ κ΅¬μ„±μ—μ„œ μš°λ¦¬λŠ” roles 메타 데이터 (roles κ°€ 핡심이고['admin']이 νŠΉμ • κ°’μž„)λ₯Ό create()λ©”μ†Œλ“œμ— μ²¨λΆ€ν–ˆμŠ΅λ‹ˆλ‹€. 이것이 μž‘λ™ν•˜λŠ” λ™μ•ˆ κ²½λ‘œμ—μ„œ 직접 @SetMetadata()λ₯Ό μ‚¬μš©ν•˜λŠ” 것은 μ’‹μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λŒ€μ‹  μ•„λž˜μ™€ 같이 μžμ‹  만의 λ°μ½”λ ˆμ΄ν„°λ₯Ό λ§Œλ“œμ‹­μ‹œμ˜€.

@@filename(roles.decorator)
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@@switch
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles) => SetMetadata('roles', roles);

이 μ ‘κ·Ό 방식은 훨씬 κΉ¨λ—ν•˜κ³  읽기 쉽고 κ°•λ ₯ν•˜κ²Œ μž…λ ₯λ©λ‹ˆλ‹€. 이제 μ»€μŠ€ν…€ @Roles()λ°μ½”λ ˆμ΄ν„°κ°€ μžˆμœΌλ―€λ‘œ 이λ₯Ό μ‚¬μš©ν•˜μ—¬ create()λ©”μ†Œλ“œλ₯Ό μž₯식 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@@filename(cats.controller)
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
@@switch
@Post()
@Roles('admin')
@Bind(Body())
async create(createCatDto) {
  this.catsService.create(createCatDto);
}

Putting it all together

이제 λŒμ•„κ°€μ„œ 이것을 RolesGuard와 ν•¨κ»˜ λ¬Άμ–΄ λ΄…μ‹œλ‹€. ν˜„μž¬λŠ” λͺ¨λ“  κ²½μš°μ— λ‹¨μˆœνžˆ trueλ₯Ό λ°˜ν™˜ν•˜μ—¬ λͺ¨λ“  μš”μ²­μ„ μ§„ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν˜„μž¬ μ‚¬μš©μžμ—κ²Œ ν• λ‹Ή 된 역할을 μ²˜λ¦¬μ€‘μΈ ν˜„μž¬ κ²½λ‘œμ— ν•„μš”ν•œ μ‹€μ œ μ—­ν• κ³Ό λΉ„κ΅ν•˜μ—¬ λ°˜ν™˜ 값을 μ‘°κ±΄λΆ€λ‘œ λ§Œλ“€κ³  μ‹ΆμŠ΅λ‹ˆλ‹€. 경둜의 μ—­ν•  (μ»€μŠ€ν…€ 메타 데이터)에 μ•‘μ„ΈμŠ€ν•˜κΈ° μœ„ν•΄ ν”„λ ˆμž„ μ›Œν¬μ— μ˜ν•΄ 제곡되고@nestjs/core νŒ¨ν‚€μ§€μ—μ„œ μ œκ³΅λ˜λŠ” Reflector 헬퍼 클래슀λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

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

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

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const hasRole = () => user.roles.some((role) => roles.includes(role));
    return user && user.roles && hasRole();
  }
}
@@switch
import { Injectable, Dependencies } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
@Dependencies(Reflector)
export class RolesGuard {
  constructor(reflector) {
    this.reflector = reflector;
  }

  canActivate(context) {
    const roles = this.reflector.get('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const hasRole = () => user.roles.some((role) => roles.includes(role));
    return user && user.roles && hasRole();
  }
}

info 힌트 node.js μ„Έκ³„μ—μ„œλŠ” κΆŒν•œμ΄ μžˆλŠ” μ‚¬μš©μžλ₯Ό request μ˜€λΈŒμ νŠΈμ— μ²¨λΆ€ν•˜λŠ” 것이 μΌλ°˜μ μž…λ‹ˆλ‹€. λ”°λΌμ„œ μœ„μ˜ μƒ˜ν”Œ μ½”λ“œμ—μ„œ request.user에 μ‚¬μš©μž μΈμŠ€ν„΄μŠ€μ™€ ν—ˆμš©λœ 역할이 ν¬ν•¨λ˜μ–΄ μžˆλ‹€κ³  κ°€μ •ν•©λ‹ˆλ‹€. μ•±μ—μ„œ μ•„λ§ˆλ„ μ‚¬μš©μž μ§€μ • 인증 κ°€λ“œ(authentication guard) (λ˜λŠ” 미듀웨어)μ—μ„œ ν•΄λ‹Ή 연결을 λ§Œλ“€ κ²ƒμž…λ‹ˆλ‹€.

Reflector 클래슀λ₯Ό μ‚¬μš©ν•˜λ©΄ μ§€μ •λœ key에 μ˜ν•΄ 메타 데이터에 μ‰½κ²Œ μ•‘μ„ΈμŠ€ν•  수 μžˆμŠ΅λ‹ˆλ‹€ (이 경우 ν‚€λŠ” rolesμž…λ‹ˆλ‹€. roles.decorator.ts파일과 κ±°κΈ°μ—μ„œ λ§Œλ“€μ–΄μ§„ SetMetadata()ν˜ΈμΆœμ„ λ‹€μ‹œ μ°Έμ‘°ν•˜μ‹­μ‹œμ˜€). μœ„μ˜ μ˜ˆμ—μ„œ ν˜„μž¬ 처리 된 μš”μ²­ λ©”μ†Œλ“œμ˜ 메타 데이터λ₯Ό μΆ”μΆœν•˜κΈ° μœ„ν•΄ context.getHandler()λ₯Ό μ „λ‹¬ν–ˆμŠ΅λ‹ˆλ‹€. getHandler()λŠ” 경둜 ν•Έλ“€λŸ¬ ν•¨μˆ˜μ— μ°Έμ‘°λ₯Ό μ œκ³΅ν•œλ‹€λŠ” 것을 κΈ°μ–΅ν•˜μ‹­μ‹œμ˜€.

컨트둀러 메타 데이터λ₯Ό μΆ”μΆœν•˜κ³  이λ₯Ό μ‚¬μš©ν•˜μ—¬ ν˜„μž¬ μ‚¬μš©μž 역할을 κ²°μ •ν•¨μœΌλ‘œμ¨ 이 κ°€λ“œλ₯Ό 보닀 일반적으둜 λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. 컨트둀러 메타 데이터λ₯Ό μΆ”μΆœν•˜κΈ° μœ„ν•΄ μš°λ¦¬λŠ” context.getHandler() λŒ€μ‹  context.getClass()λ₯Ό μ „λ‹¬ν•©λ‹ˆλ‹€:

@@filename()
const roles = this.reflector.get<string[]>('roles', context.getClass());
@@switch
const roles = this.reflector.get('roles', context.getClass());

κΆŒν•œμ΄ μΆ©λΆ„ν•˜μ§€ μ•Šμ€ μ‚¬μš©μžκ°€ μ—”λ“œ 포인트λ₯Ό μš”μ²­ν•˜λ©΄ NestλŠ” λ‹€μŒ 응닡을 μžλ™μœΌλ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€.

{
  "statusCode": 403,
  "message": "Forbidden resource"
}

λ’€μ—μ„œ κ°€λ“œκ°€ falseλ₯Ό λ°˜ν™˜ν•˜λ©΄ ν”„λ ˆμž„ μ›Œν¬μ—μ„œ ForbiddenException이 λ°œμƒν•©λ‹ˆλ‹€. λ‹€λ₯Έ 였λ₯˜ 응닡을 λ¦¬ν„΄ν•˜λ €λ©΄ κ³ μœ ν•œ μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•΄μ•Ό ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

throw new UnauthorizedException();

κ°€λ“œμ— μ˜ν•΄ λ°œμƒ 된 μ˜ˆμ™ΈλŠ” μ˜ˆμ™Έ 계측(μ „μ—­ μ˜ˆμ™Έ ν•„ν„° 및 ν˜„μž¬ μ»¨ν…μŠ€νŠΈμ— μ μš©λ˜λŠ” λͺ¨λ“  μ˜ˆμ™Έ ν•„ν„°)에 μ˜ν•΄ μ²˜λ¦¬λ©λ‹ˆλ‹€.

Last updated

Was this helpful?