개발일지
[NestJS] Guard, Decorator로 역할 기반 접근 제어(RBAC) 구현하기 본문
개요
인터넷 쇼핑몰이라고 가정한다면 상품 목록, 상세 보기 같이 모두가 접근할 수 있는 페이지가 있고 매출, 재고 관리 등 직원 전용 페이지가 있을 것이다. 이때 일반 유저는 직원 전용 페이지에 접근할 수 없어야한다. 같은 공지사항 페이지에서도 직원에게는 쓰기, 편집, 삭제 기능이 함께 노출되어야하고 일반 유저들은 읽기만 가능해야한다.
이러한 상황에서 RBAC를 사용하면 이러한 권한을 효율적이고 체계적으로 관리할 수 있다.
RBAC(Role-Based Access Control, 역할 기반 접근 제어)
사용자에게 역할을 부여하고 역할에 따라 리소스 접근 권한을 제어하는 메커니즘
RBAC 구현하기
일반 유저(user)와 직원(staff), 관리자(admin) 세가지 권한이 있다고 가정하자.
export enum Roles {
User = 'user',
Staff = 'staff',
Admin = 'admin',
}
export const ROLES_KEY = 'roles';
Decorator 구현 - 접근 허용할 역할 설정하기
먼저 메타데이터를 설정하기 위한 데코레이터를 생성해야한다.
NestJS에 내장된 SetMetadata를 사용했다. Reflector.createDecorator를 사용할 수도 있다.
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: ROLE[]) => SetMetadata(ROLES_KEY, roles);
다음과 같이 클래스(컨트롤러) 또는 핸들러 단위로 액세스 할 역할 정보를 지정해줄 수 있다.
// 클래스 단위
@Roles([Roles.Staff, Roles.Admin])
@Controller('/notice')
class NoticeController {
...
}
// 핸들러 단위
@Post()
@Roles(Roles.Admin)
async writeNotice(@Body() dto) {
await this.notiService.writeNotice(dto);
}
Guard 구현 - 접근 권한 확인하기
이제 해당 메타데이터를 가지고 유저 권한과 비교할 수 있어야 한다. NestJS의 가드를 사용해 기본 RBAC를 구현할 수 있다.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
async canActivate(context: ExecutionContext) {
// 1. Reflector로 'roles'를 키값으로 하는 메타데이터 가져오기
// handler(method) -> class(controller) 순으로 확인 (메서드 레벨 우선)
const roles =
this.reflector.get<string[]>(ROLES_KEY, context.getClass()) ||
this.reflector.get<string[]>(ROLES_KEY, context.getHandler());
// 2. roles 메타데이터 설정되지 않은 경우, 권한 확인 자체를 건너뜀
if (!roles) {
return true;
}
// 3. 해당 유저에게 부여된 역할(request.user.role) 확인
const request = context.switchToHttp().getRequest();
const userRole = request?.user?.role;
// 4. 권한 없으면 접근 거부
if (!userRole || !roles.includes(userRole)) {
return false;
}
// 5. 권한 있으면 접근 허용
return true;
}
}
적용 방법은 일반 가드처럼 UseGuards를 사용하면 된다.
@Post()
@UseGuards(RoleGuard)
@Roles(Roles.Admin)
async writeNotice(@Body() dto) {
await this.notiService.createNotice(dto);
}
사용자에게 역할 부여하기
그렇다면 유저 역할 request.user.role 은 어디서 부여해야하는걸까?
일반적으로 jwt 인증 가드를 구현할 때 디코딩된 payload를 request 객체에 저장한다.
@Injectable()
export class JwtGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
// logic...
const payload = this.jwt.verifyToken(token);
request['user'] = payload; // 유저 정보 저장
return true;
}
}
즉, 로그인할 때 jwt payload 정보로 넣어주면 된다. request.user.role에 포함된 정보를 사용하면 서비스 로직에서도 핸들링 가능하다.
// service
async writeNotice(user, dto) {
const { roles } = user;
if (roles === Roles.Admin) {
// ...
} else if (roles === Roles.Staff) {
// ...
} else {
// ...
}
}
'NestJS, Node.js > #02 NestJS' 카테고리의 다른 글
[NestJS] 역할 기반 JWT 인증 및 접근 제어 개선하기 (0) | 2025.03.10 |
---|---|
대용량 객체 파일 안전하게 업로드하기 - AWS 멀티 파트 업로드 with.NestJS (0) | 2024.10.26 |
[devlog] 객체지향 구조로 데이터 불일치 해결하고 개발 생산성 개선하기 (0) | 2024.05.25 |
[NestJS] Passport+JWT+Guard 검증 로직 커스텀하기 (1) | 2024.01.06 |
[NestJS] PASETO로 인증/인가 구현하기 (0) | 2022.11.25 |