[NestJS 공식문서 정독하기] Overview - Guards
2022. 8. 2. 17:31ㆍWeb/NestJS
반응형
🔗 https://docs.nestjs.com/guards
- guard는
CanActivate
인터페이스를 구현하며@Injectable()
데코레이터가 적용되어 있는 클래스임 - guard는 여러가지 조건들에 따라 주어진 요청이 route handler에 의해 처리될지 여부를 판단하는 역할임 (authorization & authentication)
- 기존에는 보편적으로 middleware가 해당 처리를 했지만 middleware는 멍청함 (어떤 handler에 의해 처리될지 모름)
- 그에 비해 gaurd는
ExecutionContext
를 알고 exception filter, interceptor와 유사한 구조를 가짐 - guard는 미들웨어와 interceptor/pipe 사이에 실행됨
Authorization 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);
}
}
validateRequest(request)
는 인증 로직이 포함된 함수임 (생략)- 모든 guard는
canActivate
메소드를 구현해야하며 해당 메소드는 해당 요청의 유효 여부인 boolean을 반환함
Execution context
ExecutionContext
는ArgumentsHost
상속 받음- 위 예제에서는
Request
객체에 접근하기 위해 사용함 - 여기에서
ExecutionContext
에 대해 더 자세히 알 수 있음
Role-based authentication
Binding guards
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
- guard는 controller-scoped, method-scoped, global-scoped 모두 가능
@UseGuards()
데코레이터나useGlobalGuards
메소드로 원하는 스코프에 맞게 적용할 수 있음
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
- pipe와 동일하게 DI를 활용하기 위해서 모듈을 통해 설정할 수 있음
- pipe와 동일하게 이렇게 설정할 경우 모듈과 무관하게 모두 global 스코프가 되며 guard가 정의된 모듈에서 설정하는 것을 권장함
Setting roles per handler
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
- 유연하고 재사용 가능한 방법으로 role을 route에 매칭하기 위해 custom metadata를 활용할 수 있음
- NestJS는
@SetMetadata()
를 활용해 handler에 custom metadata를 부여할 수 있는 기능을 제공함 - 위 예제에서 handler에 role이라는 metadata를 부여함
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);
}
- 다만,
@SetMetadata()
를 직접 사용하는 것은 좋은 방식이 아님 - 그래서 위와 같이 자체 데코레이터를 만들어서 사용하는 것이 좋음
- 해당 방식이 훨씬 깔끔하고 가독성이 좋으며 strongly typed임
Putting it all together
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);
}
}
- route의 custom metadata에 접근하기 위해
Reflector
helper 클래스를 활용함 (NestJS 내장) - 위 예제에서는
request.user
가 user 인스턴스와 role들을 포함한다고 가정 matchRoles(roles, user.roles)
의 로직은 user의 권한과 handler의 권한을 비교하는 로직임 (생략)- guard에서 false를 반환할 경우, 프레임워크는 자동으로
ForbiddenException
을 던짐 (다른 예외를 던지고 싶으면 다른 예외를 직접 던지면 됨) - guard에서 던지는 예외는 pipe와 마찬가지로 exception layer에서 처리하므로 exception filter가 처리함
반응형