최근 실무에서 서비스를 사용하는 사용자들을 그룹별로 나눈 뒤, 해당 그룹별로 접근 가능한 메뉴를 할당하여 접근 가능한 메뉴의 기능만 사용할 수 있도록 하는 요구사항을 받게 되었다. 우연히 최근 자격증 시험 공부를 하게 되면서 RBAC, 역할 기반 접근 제어라는 단어를 접했는데 해당 개념을 정리하고 나면 여러 작업에서 쓸모 있을 것 같아 블로그 글을 작성하게 되었다.
RBAC는 많은 서비스에 적용되어 있다. 노션을 사용하다 보면 특정 페이지는 관리자만 편집할 수 있는 권한이 있다. 관리자가 아니라면 조회만 된다거나, 특정 페이지에는 아예 접근이 불가능하기도 하다.
또는 네이버 카페와 같이 등급을 통해 특정 카테고리의 게시글을 작성/조회 등을 제한할 수 있다.
그럼 RBAC, 역할 기반 책임 제어라는 것이 무엇일까? 말 그대로 역할 별로 특정 기능에 대한 접근을 제어한다는 뜻이다. 주의할 것은 사용자 별로 접근을 제어하는 것은 아니다. 역할 별로 접근을 제어하는 것이다. 그래서 접근 권한을 해당 사용자에게 역할을 할당하는 방식으로 제공한다.
그럼 실무에서 어떻게 구현할 수 있을까?
- 기능 사용을 위해 필요한 권한을 식별한다.
- 역할 별로 필요한 권한들을 그룹핑하여 매핑한다.
- 해당 역할을 사용자에게 할당한다.
- 특정 기능에 대한 실행 요청이 들어올 때, 사용자가 매핑된 역할을 판단하여 해당 역할이 가진 권한으로 기능 실행 가능 여부를 판단하면 된다.
식당에 비유를 들어 RBAC를 알아보자.
- 기능 사용을 위해 필요한 권한 식별
- 식당에서는 메뉴판이 존재할 것이고 음식을 주문하고, 식당 관리자라면 주문들을 조회하고 손님들의 리뷰도 볼 수 있을 것이다. 위 기능들에 필요한 권한은 다음과 같을 것이다.
- 메뉴 조회
- 메뉴 추가
- 메뉴 수정
- 메뉴 삭제
- 음식 리뷰 추가
- 음식 리뷰 조회
- 음식 생성
- 주문 생성, ...
- 이것들 각각이 하나의 권한이다.
- 권한들을 역할에 매핑한다.
- 사장 - 메뉴 조회/추가/수정/삭제, 음식 리뷰 조회, 음식 생성
- 직원 - 메뉴 조회, 음식 생성
- 손님 - 메뉴 조회, 음식 리뷰 추가, 주문 생성
- 역할을 사용자에게 매핑한다.
- 사장: 한수지
- 직원: 흑곰
- 손님: 신짱구
- 기능 실행 요청 시, 접근 제어
- 만약 신짱구(손님)가 메뉴 삭제를 요청한다면?
- 신짱구의 역할을 검증한다. -> 손님
- 손님이 가진 권한을 확인한다. -> "메뉴 삭제" 권한은 없음
- "메뉴 삭제" 권한이 없음으로 해당 기능 실행 요청은 거부된다.
- 반대로, 사장인 한수지가 "메뉴 삭제"를 요청했다면?
- 한수지의 역할을 확인한다. -> 사장
- 사장 역할에 부여된 권한을 확인한다. -> "메뉴 삭제" 권한 있음
- 요청이 허용되고 메뉴가 삭제된다.
- 만약 신짱구(손님)가 메뉴 삭제를 요청한다면?
Nest에서 데코레이터, 가드를 통해 쉽게 구현해볼 수 있다.
우선은 권한을 역할에 할당하는 부분이 필요하다. 이 부분은 코드 또는 DB를 통해 관리할 수 있다.
const rolePermissions = {
사장: ['메뉴 조회', '메뉴 추가', '메뉴 수정', '메뉴 삭제', '음식 리뷰 조회', '음식 생성'],
직원: ['메뉴 조회', '음식 생성'],
손님: ['메뉴 조회', '음식 리뷰 추가', '주문 생성'],
};
User 데코레이터를 만들어 접근 가능한 역할을 할당할 수 있게 한다.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user; // 실제로는 인증 후 사용자 정보가 request.user에 할당됨
},
);
PermissionsGuard를 만들어 User 데코레이터를 통해 할당된 접근 가능한 역할을 조회하여 현재 요청 사용자의 역할을 비교한다.
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredPermissions = this.reflector.get<string[]>('permissions', context.getHandler());
if (!requiredPermissions) return true;
const request = context.switchToHttp().getRequest();
const userRole = request.user.role;
const permissions = rolePermissions[userRole] || [];
return requiredPermissions.every(permission => permissions.includes(permission));
}
}
그리고 실제 컨트롤러에 가드와 데코레이터를 적용한다.
@Controller('menu')
@UseGuards(PermissionsGuard)
export class MenuController {
@Get()
@SetMetadata('permissions', ['메뉴 조회'])
getMenu() {
return '메뉴 리스트';
}
@Delete()
@SetMetadata('permissions', ['메뉴 삭제'])
deleteMenu() {
return '메뉴 삭제됨';
}
}
RBAC는 운영자와 부운영자와 같이 범용적으로 사용되는 기능에 적용할 수 있으니, 더 깊게 공부해봐야 할 것 같다.
'study > 이거저거' 카테고리의 다른 글
엉터리 정리 - CORS (2) | 2022.05.07 |
---|---|
간단히 용어 정리 - GA, GNB, LNB, SNB, FNB (1) | 2022.01.18 |
엉터리 정리 - IIS, 레드마인 (0) | 2021.06.02 |
OAuth 2.0 - 생활코딩 강의 정리(3) (0) | 2021.05.02 |
OAuth 2.0 - 생활코딩 강의 정리(2) (0) | 2021.05.01 |