NestJS Metadata

Outclass·2022년 8월 3일
0

NestJS+GraphQL+TypeORM 

목록 보기
10/16

NestJS를 접할 수록 정말 다양한 기능들이 있다는 것에 놀라고 있다. 오늘은 Role구현 등에 이용되는 Metadata를 알아보자

Metadata?

Nest provides the ability to attach custom metadata to route handlers through the @SetMetadata() decorator. We can then access this metadata from within our class to make certain decisions. - Nest공식문서

Meatadata는 루트 핸들러에 첨부되며, 이 Metadata에 접근하면 특정한 결정을 내릴 수 있는 기능을 구현할 수 있다는 이야기이다. 개념적으로 접근하면 다소 난해할 수 있기 때문에 간단한 구현을 통해 이해해보자

사용법

Role에 따른 접근 권한 구현을 대표적인 예로 들 수 있다. 아래 구현을 보자

기본구현

//cats.controller.ts
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
  • 기본적으로 Metadata는 컨트롤러에서 사용된다.
  • @SetMetadata() 데코레이터를 통해 사용할 수 있다.
  • Metadata내의 'roles'라는 키가 존재하고, 그 roles중 'admin'의 값을 가진 경우에만 controller를 통과할 수 있다.

코드개선하기

공식문서에서는 코드 가독성 및 재사용성을 높이기 위해 아래와 같이 Custom Decorator를 만들어서 구현하는 것을 권장하고 있다.

//roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

type UserRole = 'guest' | 'admin' | 'user' | 'any'

export const Roles = (roles: UserRole[]) => SetMetadata('roles', roles);
  • type으로 특정 텍스트를 지정해서 제약조건을 만들 수 있다.
//cats.controller.ts
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
  • custom decorator로 코드의 가독성이 개선된 것을 볼 수 있다.

Guard와 함께 사용

위에서 설정한 Metadata를 이용하여 권한관리 등을 하려면, Guard와 함께 사용해야 한다.

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}
  canActivate(context: ExecutionContext) {
    //metadata에 저장한 키로 reflector에서 불러오기
    const roles = this.reflector.get<AllowedRoles>(
      'roles',
      context.getHandler(),
    );
    if (!roles) {
      return true;
    }
    const getContext = GqlExecutionContext.create(context).getContext();
    const user = getContext['user'];
    if (!user) {
      return false;
    }
    if (roles.includes('Any')) {
      return true;
    }
    return roles.includes(user.role);
  }
}
//auth.module.ts 
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';

@Module({
  //guard를 app모든 곳에서 사용하고 싶다면
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AuthModule {}
//app.module.ts
@Module({
  ...
  imports : [
    ...
    AuthModule,
    ...
  ]
  ...
})
  • 기존에 생성한 AuthGuard와 함께 구현할 수 있다.
  • Reflector를 불러와서 지정한 key를 통해 값에 접근할 수 있다.
  • 위의 예시들과 같이 구현하면, 지정된 role이 아닌 사용자가 create에 접근할 경우 guard에서 forbidden을 띄우고 접근을 차단한다.

사용후기

잘 활용하면 굉장히 기능적으로 편리할 것 같다고 느꼈다. 특히나 유저의 Role에 따른 특정 기능에 대한 접근 제한을 구현하는데 있어서 아주 복잡한 처리를 하지 않아도 간결하게 구현할 수 있다는 점에서 아주 좋은 기능이라는 생각이 든다.

profile
When you stop having big dreams that’s when you’ve died, despite not being buried yet.

0개의 댓글