사용자에게 권한을 직접 주지 않고 역할(Role)을 통해 부여하는 접근 제어 모델 (Role-Based Access Control).
구조
사용자 ──── 역할 ──── 권한
김철수 ──► ADMIN ──► [회원관리, 결제조회, 설정변경, 로그조회]
이영희 ──► MANAGER ──► [결제조회, 로그조회]
박민수 ──► USER ──► [본인정보조회]
중간에 역할을 둔 것이 전부지만, 이 한 겹이 큰 차이를 만든다.
직접 부여와 비교
직접 부여 (역할 없음) 직원 500명 × 권한 30개 = 최대 15,000개의 연결을 관리 새 권한이 생기면 해당하는 사람 전부에게 일일이 부여
RBAC 직원 500명 → 역할 10개 → 권한 30개 새 권한은 역할에만 추가하면 끝. 그 역할을 가진 전원에게 즉시 적용
관리 지점이 사용자 수가 아니라 역할 수에 비례한다.
인사 이동도 간단하다 — 역할만 바꾸면 이전 권한이 한 번에 정리된다. 직접 부여 방식에서는 떼어내는 것을 빠뜨려 권한이 누적되는 일이 흔하다 (privilege creep).
표준 모델의 네 단계
| 단계 | 추가되는 것 |
|---|---|
| Core RBAC | 사용자–역할–권한 기본 구조 |
| Hierarchical | 역할 상속 (ADMIN이 USER 권한을 포함) |
| Constrained | 직무 분리(SoD) — 결재 요청자와 승인자를 겸할 수 없음 |
| Symmetric | 권한 기준 조회(이 권한을 누가 갖고 있나) |
직무 분리는 금융·회계 시스템에서 감사 요건이기도 하다.
스프링 시큐리티에서 — Role과 Authority
@PreAuthorize("hasRole('ADMIN')") // 내부적으로 "ROLE_ADMIN" 을 찾는다
@PreAuthorize("hasAuthority('order:read')") // 접두사 없이 그대로
hasRole은 자동으로 ROLE_ 접두사를 붙인다. DB에 ADMIN으로 저장했는데
ROLE_ADMIN으로 조회되어 권한이 안 먹는 것이 초심자의 단골 실수다.
실무 설계는 보통 이렇게 나눈다.
-
Role — : ROLE_ADMIN, ROLE_USER (거친 구분) Authority : order:read, order:write (세밀한 권한)
-
역할이 권한들의 묶음이 된다
RBAC의 한계 — 역할 폭발
조건이 늘수록 역할을 계속 쪼개야 한다.
ADMIN ADMIN_SEOUL ADMIN_SEOUL_READONLY ADMIN_SEOUL_READONLY_NIGHT …
이 지경이 되면 RBAC의 관리 이점이 사라진다. → ABAC(속성 기반)으로 조건을 표현하거나, 역할 + 속성을 조합한다.
소유권은 RBAC로 표현할 수 없다
"자기 주문만 조회 가능"
- 역할로는 표현이 불가능하다 — 모든 USER가 같은 역할이므로
- 애플리케이션에서 소유권을 별도로 검사해야 한다
"역할로 큰 문을 열고, 소유권으로 방문을 연다" 는 2단 구조가 실무의 표준이다. IDOR 취약점이 대부분 이 두 번째 검사를 빠뜨려 발생한다.
권한 변경의 즉시 반영
JWT에 역할을 넣어 두면 권한을 회수해도 토큰 만료까지 유효하다. 민감한 권한 변경(관리자 해임 등)은
- Access Token 수명을 짧게 가져가거나
- 중요한 작업에서만 DB를 다시 조회해 확인한다