역할 기반 접근 제어 (RBAC)
1. 개요
Lumie 백엔드는 두 계층의 접근 제어를 사용합니다.
- Role 계층:
libs/common의Role열거형으로 정의되는 수직 권한 계층 — JWTroleclaim으로 전파 - Permission 코드:
staff모듈의permissions테이블에 저장되는 세분화된 기능 접근 권한 — 직원(staff)별로AccessLevel(NONE / READ / WRITE) 부여
2. Role 정의
Role 열거형은 libs/common/src/main/java/com/lumie/common/auth/Role.java에 위치합니다. 모든 모듈이 auth 모듈에 의존하지 않고도 역할을 참조할 수 있도록 공용 라이브러리에 있습니다.
// Role.java — libs/common
public enum Role {
OWNER(0), // 원장 — 학원 전체 최고 권한자
MANAGER(1), // 부원장 — 위임된 범위 관리
INSTRUCTOR(2), // 강사 — 담당 반 내 운영
STUDENT(3); // 학생 — 본인 정보 조회
/**
* @return true if this role has equal-or-higher authority than required
* (OWNER.hasAuthority(INSTRUCTOR) == true)
*/
public boolean hasAuthority(Role required) {
return this.level <= required.level;
}
/**
* @return true if this role strictly outranks target
* (OWNER.canManage(MANAGER) == true, INSTRUCTOR.canManage(INSTRUCTOR) == false)
*/
public boolean canManage(Role target) {
return this.level < target.level;
}
}
숫자가 낮을수록 상위 권한입니다. hasAuthority는 상위 역할이 하위 역할의 권한을 자동 포함합니다.
3. 토큰 전파
Role은 JWT 토큰의 role claim에 포함되어 모든 요청에 전파됩니다. JwtAuthenticationFilter가 UserContextHolder.setUserRole(claims.role().name())을 호출하여 스레드 로컬에 저장합니다.
// 서비스 레이어에서 현재 역할 조회
Role currentRole = UserContextHolder.getUserRole();
// 권한 확인 예시 — MANAGER 이상만 허용
if (!currentRole.hasAuthority(Role.MANAGER)) {
throw new BusinessException(CommonErrorCode.FORBIDDEN);
}
// OWNER 전용 작업
AuthorizationGuard.requireOwner(); // libs/common 유틸 — OWNER 아니면 FORBIDDEN
4. 직원 등록 가능 역할
직원 등록 API(staff 모듈)는 MANAGER, INSTRUCTOR 만 생성할 수 있습니다. OWNER는 테넌트 가입 시 시스템이 자동 생성합니다.
// StaffCommandService.java
private static final EnumSet<Role> CREATABLE_ROLES = EnumSet.of(Role.MANAGER, Role.INSTRUCTOR);
등록자 역할이 생성 대상 역할을 canManage(targetRole) 로 검사합니다. 동급 역할 생성이 차단됩니다.
5. 세분화된 퍼미션 (staff 모듈)
직원 단위의 세분화된 기능 접근 권한은 permissions 테이블과 staff_permissions 조인 테이블로 관리됩니다.
// AccessLevel.java — staff 모듈
public enum AccessLevel {
NONE, READ, WRITE
}
StaffService.getStaffPermissions(tenantSlug, staffId) 는 직원의 퍼미션 코드 목록을 반환합니다.
record StaffPermissionsData(
Long staffId,
List<PermissionEntry> permissions // {permissionCode, accessLevel}
) {}
퍼미션 코드와 카테고리는 permissions.code, permissions.category 컬럼으로 테넌트 스코프 내에서 관리됩니다.
6. Spring Security 엔드포인트 인가
현재 SecurityConfig는 경로 레벨에서 ROLE_INTERNAL(내부 worker 콜백) 외에 별도의 role 기반 인가 규칙을 선언하지 않습니다. 세분화된 접근 제어는 서비스 레이어에서 UserContextHolder.getUserRole()과 Role.hasAuthority() / AuthorizationGuard.requireOwner() 를 통해 구현합니다.
// SecurityConfig.java — 주요 인가 규칙
.requestMatchers("/internal/**").hasRole("INTERNAL") // HMAC 인증 경로
.anyRequest().authenticated() // 나머지는 JWT 인증만 요구
7. Frontend 참고사항
- RoleSchema:
entities/session/model/schema.ts useIsAdmin()훅: OWNER, MANAGER, INSTRUCTOR를 관리자측으로 판별
const ADMIN_ROLES = ['OWNER', 'MANAGER', 'INSTRUCTOR'];
관련 문서
- 인증 & 멀티테넌시 — JWT Claims 구조, 필터 설정
- 모듈 레퍼런스 — staff 모듈 — StaffService, PermissionQueryService