[NestJS] Guards

hahaha·2021년 8월 1일
0

NestJS

목록 보기
6/11
post-thumbnail

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() 메서드 사용
// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
// 의존성 주입을 고려한 모든 모듈에서의 가드 설정
// app.module.ts
@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() 사용하는 것 권장하지 않음
    -> 데코레이터 생성
// roles.decorator.ts
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 헬퍼 클래스 사용
//roles.guard.ts
@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();
    
    // node.js에서는 승인된 사용자를 request 객체에 연결하는 것이 일반적
    const user = request.user;
    
    //return matchRoles(roles, user.roles);
    
    // 간단하게 구현한 matchRoles 로직
    return roles.includes(user.role);
  }
}

활용: Authorization guard + Role-based authentication

// auth/local-auth.guard.ts
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

// auth/jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

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

// app.controller.ts
@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를 통해 역할 인증 전략에서 판단될 역할 명시
profile
junior backend-developer 👶💻

0개의 댓글