이전까지 AuthGuard
를 만들어 @UseGuard()
데코레이터로 적용한 Authentication 과정을 진행하였습니다. 이번 글에서는 Role-based access control 메커니즘을 적용하여 역할과 권한에 대해 정의하여 액세스를 제어해 봅시다.
먼저 User Entity에 Role
이라는 권한(역할) Column을 추가해줍니다.
// src/user/entity/user.entity.ts
export enum UserRole {
CLIENT = 'CLIENT',
ADMIN = 'ADMIN',
}
@Entity({ name: 'users' })
export class User {
// ....
@Column({
type: 'enum',
enum: UserRole,
default: UserRole.CLIENT,
})
role: UserRole;
// ...
}
@Role()
이라는 Decorator를 만듭니다. 이 데코레이터는 지정한 특정 리소스에 대해 필요한 role(권한)을 허가합니다.
예를 들어 admin
이 필요한 리소스에 접근하기 위해 사용자는 role
column에 admin
으로 지정이 되어야 하죠.
// src/auth/role.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { UserRole } from 'src/user/entity/user.entity';
// 'Any' : 모든 role에 대해 허용
export type AllowedRole = keyof typeof UserRole | 'Any';
export const Role = (roles: AllowedRole[]) => SetMetadata('roles', roles);
참고 🔍
- Nest 는
@SetMetadata()
decorator를 통해 route handler에 custom metadata를 붙일 수 있도록 기능을 제공합니다.- NestJS Docs - Excution context(Reflection and metadata)
roles
custom metadata에 접근하기 위해, framework 밖에서 제공하는 Reflector
헬퍼 클레스를 사용합니다. // src/auth/auth.guard.ts
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {} // `roles` custom metadata에 접근하기 위해 reflector 사용
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
//
const requiredRoles = this.reflector.get<AllowedRole>(
'roles',
context.getHandler(),
);
if (!requiredRoles) { // role이 필요없는 resource면 그냥 넘어감
return true;
}
const request = context.switchToHttp().getRequest();
const user: User = request['user'];
if (!user)
return false;
if (requiredRoles.includes('Any'))
return true; // `Any` 권한은 로그인된 사용자 중 아무나 접근이 가능하다는 뜻.
return requiredRoles.includes(user.role); // 사용자의 role이 resource가 필요한 권한에 포함되어있는지 검사!
}
}
// src/user/user.controller.ts
@Controller('user')
export class UserController {
// ...
@Get('/me')
@Role([UserRole.CLIENT])
@UseGuards(AuthGuard)
getMe() {
return 'Get Me!';
}
@Get('/admin')
@Role([UserRole.ADMIN])
@UseGuards(AuthGuard)
getAdmin() {
return `U R Admin`;
}
}
만약 현재 방식으로 swagger나 다른 decorator를 Auth 과정을 함께 쓴다면 controller는 골뱅이 무침이 될 것 같습니다.
그래서 우리는 Auth에 필요한 Decorator를 모~두 묶어 제공해봅시다.
Custom Decorator를 만들어 봅시다.
// src/auth/auth.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { AllowedRole, Role } from './role.guard';
export function Auth(roles: AllowedRole[]) {
return applyDecorators(Role(roles), UseGuards(AuthGuard));
}
참고 🔍
@Controller('user')
export class UserController {
// ...
@Get('/me')
// @Role([UserRole.CLIENT])
// @UseGuards(AuthGuard) 밑에꺼로 단축 가능
@Auth([UserRole.CLIENT])
getMe() {
return 'Get Me!';
}
@Get('/admin')
@Auth([UserRole.ADMIN])
getAdmin() {
return `U R Admin`;
}
}
참고 🔍
골뱅이 무침ㅋㅋㅋㅋ 글 잘 봤습니다.