Guards란?
NestJS docs - Guards
@Injectable()
데코레이터가 달린 클래스
CanActivate
인터페이스를 구현해야 함
- 단일 책임 존재
- 특정조건에 따라 요청을 라우터 핸들러에 의해 처리할지 여부 결정 => 승인(Authorization)
- ex. 특정조건: 권한, 역할, ACL(Access Control List), ...
- 승인(및 인증)은 일반적으로 기존 Express 애플리케이션의 middleware에 의해 처리됨
- 단점: 미들웨어는 next()
함수 호출 후, 어떤 핸들러가 실행될지 알 수 없음
-> 가드는 ExecutionContext
인스턴스에 접근 가능하기에 다음에 실행될 작업을 정확히 알고 있음
Authorization guard
- 승인: 충분한 권한이 있는 호출자만 특정 라우터 사용 가능
- 모든 가드는
canActivate()
함수 구현해야 함
- 단일 인수로 ExecutionContext
인스턴스를 받음
- 현재 요청 허용 여부를 boolean값으로 반환
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
Role-based authentication
- 특정 역할을 가진 사용자에게만 엑세스를 허용하는 (보다 기능적인)가드
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
Binding guards
- 컨트롤러 범위 가드 설정
- 해당 컨트롤러가 선언한 모든 핸들러에 가드 연결됨
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
- 단일 메서드 가드 설정: 메서드 수준에서
@UseGuards()
적용
- 전역가드 설정:
useGlobalGuards()
메서드 사용
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
Setting roles per handler
- 라우트 핸들러에 커스텀 메타데이터 첨부
- 라우트마다 다른 권한체계(관리자, 사용자, ...) 설정
@SetMetadata()
데코레이터 이용
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
- 라우트에서 직접
@SetMetadata()
사용하는 것 권장하지 않음
-> 데코레이터 생성
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Putting it all together
- RolesGuards 재정의
- 현재 사용자에게 할당된 역할과 라우트에 필요한 실제 역할 비교하는 로직 추가
- 라우트의 역할에 접근하기 위한
Reflector
헬퍼 클래스 사용
@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 false;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return roles.includes(user.role);
}
}
활용: Authorization guard + Role-based authentication
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Controller()
export class AppController {
constructor(private authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Request() req) {
return this.authService.login(req.user);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
}
@UseGuards
를 통해 사용자 인증 전략과 역할 인증 전략 동시 설정 가능
- 직접 만든
@Roles
를 통해 역할 인증 전략에서 판단될 역할 명시