[NestJS docs] Guards

nakkim·2022년 7월 18일
0

NestJS docs

목록 보기
6/10
post-custom-banner

https://docs.nestjs.com/guards

세 줄 요약?

  1. 가드를 사용해서 요청 처리 여부를 결정할 수 있음
  2. 유저의 권한에 따라서 다르게 결정 가능
  3. @SetMetadata()로 경로마다 다른 권한 체계 설정 가능?

Guards

가드는 단일 책임

라우트 핸들러의 요청 처리 여부를 결정한다.

Express에서 미들웨어로 처리하던 것

미들웨어와의 차이점

  • 미들웨어: 다음(next() 호출 후)에 어떤 핸들러가 올지 모름
  • 가드: ExecutionContext 인스턴스에 접근할 수 있기 때문에 다음에 실행될 것이 뭔지 안다.

가드 실행 순서
all middleware → guard → interceptor or pipe


Authorization guard

AuthGuard는 요청이 인증된 사용자(요청 헤더에 토큰 첨부됨)라고 가정, 토큰을 추출 및 검증하고 추출된 정보를 이용해서 요청의 처리 여부를 결정한다.

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

위 코드에서 validateRequest() 함수는 알아서 구현하삼

이 예제는 가드가 요청/응답 주기에서 어떻게 동작하는지를 보여준다.

모든 가드는 canActivate() 함수를 구현해야 한다. 이 함수는 요청 처리 여부를 boolean으로 리턴한다. (동기 또는 비동기로 리턴 가능)

  • Nest는 리턴값이 true일 경우 요청 진행, 아니면 거절

Execution context

canActivate() 함수는 ExecutionContext 인스턴스를 인수로 사용한다. ExecutionContextArgumentsHost를 extend함 (→ 여러 helper 메서드가 추가됨)

ArgumentsHost는 Exception filters에서 봤듯이, Request 객체를 얻기 위해 사용한다.


Role-based authentication

유저가 특정 권한을 가지고 있을 때만 허용하는 가드를 만들어보자.

아래는 가드 기본 템플릿

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

Binding guards

파이프나 예외 필터처럼, 가드를 method-scoped, controller-scoped, global-scoped로 만들 수 있다. 지금은 controller-scoped를 만들어 보자.

@Controller('cats')
@UseGuards(RolesGuard)
/**
 * 인스턴스를 넘겨줄 수도 있음
 * @UseGuards(new RolesGuard())
 */
export class CatsController {}

RolesGuard 인스턴스 대신 타입을 넘겨줌으로써, 프레임워크에게 인스턴스화에 대한 책임을 넘기고 DI를 활성화한다.

글로벌 레벨로 만들고 싶다? Nest 앱의 useGlobalGuards() 메서드를 사용하면 됩니다~

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

하지만 모듈 외부에서 등록되었기 때문에, 의존성 주입이 안됨!

이를 해결하기 위해서는 모듈에서 가드를 설정하면 된다.

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

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

어떤 모듈에 설정하든, 가드는 전역적임

Setting roles per handler

이제 RolesGuard가 작동한다. 그치만.. 모자라

가드의 쩌는 기능 - execution context를 사용해보자.

라우트마다 확인해야 하는 권한이 다르다면 어떻게 해야 할까?

@SetMetadata() 데코레이터를 이용하면 라우트 핸들러에 custom metadata를 붙일 수 있다. (role 데이터 제공)

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

create() 메서드에 roles 메타데이터(key: roles, value: [’admin’])를 추가했다.

이것도 작동은 하지만.. @SetMetadata()를 직접 사용하기 보다는, 데코레이터를 만들어서 쓰는 것이 낫다.

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

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

이게 좀 더 보기 좋지?^^

@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

Putting it all together

이제 RolesGuard와 함께 연결해보자. 현재는 모든 권한에 대해 true를 리턴한다. 이것을 유저 권한과 요청 라우트에 필요한 권한을 비교하여 리턴하는 것으로 바꿔보자. 라우트의 권한(custom metadata)에 접근하기 위해 Reflector helper class를 이용할 것이다.

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private 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;
		// 유저의 권한과 필요 권한 비교 - 알아서 구현
    return matchRoles(roles, user.roles);
  }
}

node.js에서는 Request 객체에 유저를 추가하는 것이 일반적이기 때문에, 위 예제에서 request.user에 유저 인스턴스와 권한이 포함되어 있다고 가정한다.

가드가 false를 반환하는 경우, 프레임워크는 ForbiddenException을 발생시킨다. 다른 에러 응답을 보내려면, 다른 예외를 던지시오

throw new UnauthorizedException();

가드가 던진 모든 예외는 예외 계층에서 처리된다.

profile
nakkim.hashnode.dev로 이사합니다
post-custom-banner

0개의 댓글