멀티테넌트 회원·인증 설계 3편 – 테넌트별 역할/권한(RBAC) 모델링 패턴

·2025년 11월 27일
post-thumbnail

🧬 개요

1편에서 tenant / user / tenant_user회원·테넌트 도메인을 정리했고,
2편에서 그 위에 JWT + Redis 인증 구조를 얹었다.

3편에서는 그 다음 단계인 권한(Authorization) 을 다룬다.

  • 멀티테넌트 환경에서 역할/권한을 어떻게 쪼갤지
  • tenant_user와 역할을 어떻게 연결할지
  • “플랫폼 전체 관리자” vs “테넌트 관리자”를 어떻게 구분할지
  • 역할·권한을 토큰/JWT와 어떻게 연결할지

RBAC(Role-Based Access Control) 관점에서 정리한다.


✨ TL;DR

  • 멀티테넌트 RBAC의 기본 축은 보통 이렇게 잡을 수 있다.
    • user (회원)
    • tenant (서비스/조직/고객사)
    • tenant_user (테넌트 내 회원 관계)
    • role (역할)
    • permission (행동/권한 단위)
  • 실제 모델은 다음 중 하나, 혹은 조합으로 간다.
    1. 글로벌 역할 + 테넌트 공용
      ROLE_TENANT_ADMIN, ROLE_TENANT_USER 같은 고정 역할
    2. 테넌트별 커스텀 역할
      – 각 테넌트가 역할 이름/권한을 직접 구성 (B2B SaaS에서 자주 등장)
  • 설계 시 고민 포인트:
    • 역할/권한을 얼마나 세밀하게 쪼갤지
    • 테넌트마다 얼마나 자유롭게 커스터마이징하게 할지
    • 권한 체크를 도메인 레벨에서 할지, 프레임워크(Spring Security 등)에 위임할지
  • “무조건 복잡하게”보다는,
    • 작은 역할 세트 + 명확한 규칙으로 시작하고
    • 필요해질 때 커스텀 역할/권한 테이블을 확장하는 전략도 충분히 현실적인 선택이다.

1. RBAC 기본 축 정리

RBAC를 아주 단순화하면,
결국 다음 네 가지를 조합해서 “허용/거부”를 판단한다.

  1. 주체(Subject)
    • 누가? → user, tenant_user, role
  2. 리소스(Resource)
    • 무엇에 대해? → 예: 코스, 게시글, 주문, 사용자 계정…
  3. 행동(Action)
    • 무엇을 하려는가? → READ, WRITE, DELETE, APPROVE
  4. 컨텍스트(Context)
    • 어떤 상황에서? → tenant_id, 소유자 여부, 시간대, IP, 플랜 등

이 글에서는 이 중 주체·컨텍스트에 집중한다.
(리소스/행동은 도메인마다 달라서, 패턴만 간단히 짚고 간다.)


2. 멀티테넌트 RBAC 도메인 모델 예시

1편에서 썼던 도메인에 역할/권한을 붙이면
대략 이런 모델이 된다.

user
---------
id
email
...

tenant
---------
id
code
name
...

tenant_user
---------
id
tenant_id (FK)
user_id   (FK)
status          -- 가입, 정지 등
...

role
---------
id
code           -- ex) TENANT_ADMIN, TENANT_USER
name
scope          -- GLOBAL / TENANT
...

permission
---------
id
code           -- ex) COURSE_READ, COURSE_WRITE
name

role_permission      -- 역할이 어떤 권한을 가지는지
---------------
role_id       (FK)
permission_id (FK)

tenant_user_role     -- 테넌트 내 회원의 역할
-----------------
tenant_user_id (FK)
role_id        (FK)

핵심 개념은:

  • role

    • “이 사용자가 어떤 일을 할 수 있는지”를 묶은 이름
    • ex) TENANT_ADMIN, TENANT_MANAGER, TENANT_USER, PLATFORM_ADMIN
  • permission

    • 실제 액션 단위 (COURSE_READ, COURSE_WRITE, USER_INVITE …)
  • role_permission

    • “어떤 역할이 어떤 permission을 포함하는지”
  • tenant_user_role

    • “이 테넌트 안에서 이 회원이 어떤 역할을 가지는지”

이 구조 하나로:

  • 한 회원이
  • 여러 테넌트에 가입하고
  • 테넌트마다 서로 다른 역할을 갖는 상황

까지 표현할 수 있다.


3. 글로벌 역할 vs 테넌트 역할 – 스코프 나누기

멀티테넌트 서비스에서는 보통 두 종류의 역할이 등장한다.

  1. 플랫폼 전체에서 공통으로 쓰는 역할 (GLOBAL)
  2. 각 테넌트 안에서만 의미가 있는 역할 (TENANT)

이를 role.scope 같은 필드로 구분할 수 있다.

role
---------
id
code     -- ex) PLATFORM_ADMIN, TENANT_ADMIN, TENANT_USER
scope    -- GLOBAL / TENANT

3.1 글로벌 역할 예시

  • PLATFORM_ADMIN

    • 모든 테넌트/모든 리소스에 접근 가능한 플랫폼 운영자
  • SUPPORT

    • 고객 지원 업무를 위해 여러 테넌트 데이터를 조회할 수 있는 역할

이 역할들은 보통 운영자 콘솔에서 사용되고,
일반 회원에게는 부여되지 않는다.

3.2 테넌트 역할 예시

  • TENANT_ADMIN

    • 해당 고객사/조직의 관리자
  • TENANT_MANAGER

    • 일부 관리 기능만 가능한 중간 역할
  • TENANT_USER

    • 일반 사용자

이 역할들은 각 테넌트의 관리자 화면에서,
해당 조직의 구성원에게 부여되는 역할이다.


4. 역할 설계 전략: 고정 역할 vs 커스텀 역할

여기서 중요한 선택지가 하나 나온다.

테넌트별로 역할 구성을 고정할 것인지,
아니면 각 테넌트가 직접 정의하게 할 것인지.

4.1 전략 A – 플랫폼에서 고정된 역할 세트 제공

예:

  • TENANT_ADMIN
  • TENANT_MANAGER
  • TENANT_USER

만 제공하고, 테넌트는 이 역할을 구성원에게 할당만 할 수 있음.

장점

  • 도메인/코드가 단순하고, 정책 설명이 쉽다.
  • 화면·API·권한 체크가 모두 미리 정의된 역할을 기준으로 돌아간다.
  • 운영/디버깅 시 “어떤 권한을 가진 사람인지” 파악이 빠르다.

단점

  • 고객사의 “조직 구조/권한 체계”를 세밀하게 반영하기 어렵다.

  • 도입 초기에는 문제 없지만, 큰 고객사가 들어오면

    • “팀장/부팀장/게스트/외부 파트너 등 더 쪼개고 싶다”
      는 요구가 나올 수 있다.

언제 적합한가

  • 초기 SaaS 제품,
  • 비교적 단순한 권한 구조,
  • 내부 서비스 또는 B2C에 가까운 형태일 때.

4.2 전략 B – 테넌트별 커스텀 역할 허용

각 테넌트가 자체적으로 역할 이름/권한 세트를 구성하도록 하는 패턴.

tenant_role
---------
id
tenant_id (FK)
code      -- ex) ADMIN, MANAGER, VIEWER (tenant 내부 코드)
name

tenant_role_permission
-------------------
tenant_role_id (FK)
permission_id  (FK)

(혹은 role 테이블에 tenant_id 컬럼을 두고, NULL이면 글로벌 역할로 보는 방식도 가능하다.)

장점

  • 고객사가 원하는 권한 세분화를 그대로 반영할 수 있다.

  • 큰 B2B 고객사 입장에서는

    • “우리는 역할을 이렇게 쓰고 싶다”를 실현하기 쉬움.
  • 시스템은 permission 단위로만 API 보호를 설계하고
    역할 구성은 테넌트에 위임할 수 있다.

단점

  • 운영/디버깅 난이도가 확 올라간다.

    • 테넌트마다 역할 구성이 다르기 때문에
      “이 계정이 왜 이 기능을 못/할 수 있는지”를 확인해야 할 때
      DB를 더 뒤져봐야 한다.
  • UI/UX도 복잡해진다.

    • “역할 템플릿”, “복사”, “추천 역할 세트” 같은 기능이 필요할 수 있다.
  • 토큰에 실어 보내는 역할/권한 정보도
    테넌트마다 달라지므로 캐싱 전략이 중요해진다.

언제 적합한가

  • B2B SaaS,
  • 고객사별 조직/권한 요구가 복잡하고,
  • “역할 커스터마이징”이 상품 경쟁력의 일부일 때.

5. 권한(permission)을 얼마나 세밀하게 쪼갤 것인가

permission을 정의할 때 자주 하는 고민:

“권한을 API 단위까지 쪼갤까,
아니면 기능 묶음 단위로만 볼까?”

5.1 너무 세밀한 권한

예:

  • COURSE_CREATE
  • COURSE_UPDATE
  • COURSE_DELETE
  • COURSE_ASSIGN_TEACHER
  • COURSE_PUBLISH

장점

  • 권한 구성을 아주 유연하게 만들 수 있다.
  • 고객사가 “삭제는 안 되게 해주세요” 같은 요구를 했을 때 대응 가능.

단점

  • 역할 설계/설명/테스트가 급격히 복잡해진다.
  • 실제론 대부분 “같이 움직이는 권한”이라, 관리 오버헤드만 늘어날 수 있다.

5.2 기능 단위로 적당히 묶인 권한

예:

  • COURSE_MANAGE (생성/수정/삭제/배정 등 포함)
  • COURSE_VIEW
  • USER_MANAGE
  • USER_VIEW

장점

  • 도메인 코드/정책 설명이 단순하다.
  • 역할 정의도 “VIEW vs MANAGE” 수준으로 직관적이다.

단점

  • 일부 고객 요구(예: “삭제만 막고 싶어요”)는 대응이 어렵다.
  • 이 경우에는 아예 “상위 플랜에서만 제공” 같은 비즈니스 결정으로 풀 수도 있다.

현실적인 접근

  • 가장 자주 나올 기능 단위 위주로 permission을 먼저 정의하고,
  • 정말 필요해지는 순간에만 세분화된 permission을 추가하는 방식이
    구현·운영 모두에서 부담이 덜하다.

6. 권한 체크를 어디에서 할 것인가

권한 체크는 크게 두 레이어에서 할 수 있다.

  1. 프레임워크/인프라 레벨 – 예: Spring Security @PreAuthorize
  2. 도메인 레벨 – 서비스/도메인 메서드에서 직접 체크

6.1 프레임워크 레벨 체크

예를 들어 Spring Security를 쓰면:

@PreAuthorize("hasRole('TENANT_ADMIN')")
@GetMapping("/tenants/{tenantId}/users")
public List<UserDto> listTenantUsers(...) { ... }

장점

  • 코드 상에서 “이 API는 어떤 역할이 호출할 수 있는지”를 한눈에 볼 수 있다.
  • 필터/인터셉터 레벨에 있기 때문에 일관성이 높다.

단점

  • “자기 소유 데이터만 접근 가능” 같은
    도메인 조건이 섞인 권한 체크는 표현이 어렵다.
  • 커스텀 역할/permission 모델과 1:1로 연결하기 위해
    커스텀 PermissionEvaluator 등을 추가로 구현해야 할 수 있다.

6.2 도메인 레벨 체크

예:

public Course findCourseForUser(TenantContext ctx, Long courseId) {
    Course course = courseRepository.findByIdAndTenantId(courseId, ctx.getTenantId());
    if (!authorizationService.canViewCourse(ctx.getTenantUserId(), course)) {
        throw new AccessDeniedException();
    }
    return course;
}

장점

  • “테넌트 + 소유자 + 상태 + 플랜” 등
    도메인 맥락을 모두 고려한 권한 체크를 구현하기 쉽다.
  • 테스트도 도메인 단위로 작성 가능.

단점

  • 컨트롤러/서비스 곳곳에서 권한 체크 호출을 깜빡할 위험이 있다.
  • 프레임워크 도움 없이 순수 코드로만 보안이 지켜지고 있기 때문에
    코드 리뷰/테스트에 더 신경 써야 한다.

현실적인 조합

  • “이 API는 어떤 역할 이상만 들어올 수 있다” 같은
    1차 필터링은 프레임워크에서,
  • “이 리소스에 진짜 접근 가능한 사람인가?” 같은
    도메인 조건은 서비스/도메인 레벨에서
    이중으로 체크하는 패턴이 많이 쓰인다.

7. 토큰(JWT)과 RBAC 연결하기

2편에서 Access Token 클레임 예시는 대략 이랬다.

{
  "sub": "user-12345",
  "tenant_id": "tenant-001",
  "tenant_user_id": "tu-7890",
  "roles": ["TENANT_ADMIN", "TENANT_USER"],
  "iat": 1730000000,
  "exp": 1730003600
}

여기서 선택지는 두 가지 정도다.

7.1 토큰에 역할만 넣고, permission은 서버에서 조회

  • JWT에는 roles만 넣고,
  • 실제 권한 체크는 서버에서 role -> permission 매핑을 조회

장점

  • 역할 구성이 수정돼도 기존 토큰을 그대로 쓸 수 있다.
  • permission 목록이 매우 길어질 때 토큰이 비대해지는 걸 막을 수 있다.

단점

  • 권한 체크 시 매번 DB/캐시를 조회해야 한다.
  • 캐싱 전략이 중요해진다. (예: Redis에 role → permission 매핑 캐싱)

7.2 토큰에 permission까지 직접 넣기

  • 로그인 시점에 사용자/테넌트의 permission 목록을 계산해서 토큰에 넣는다.

장점

  • API 호출 시 토큰만 보고 권한 체크가 가능 → 빠름.
  • 서버에서 role/permission 테이블을 조회할 필요가 줄어든다.

단점

  • permission 구성이 바뀌어도
    토큰 만료 전까지는 예전 권한이 유지될 수 있다.
  • permission 목록이 길어지면 토큰 크기가 커진다.

실무에서는:

  • 역할만 토큰에 넣고, permission은 서버/캐시에서 조회하는 패턴이
    유지 보수 관점에서 조금 더 많이 선택되는 편이다.
    (특히 B2B에서 역할/권한 구성이 자주 바뀌는 경우)

8. 설계 체크리스트 (실무 관점)

지금 멀티테넌트 RBAC를 새로 설계하거나, 기존 구조를 다듬고 있다면
아래 질문들을 한 번씩 점검해볼 만하다.

  1. 역할 스코프

    • 플랫폼 전체에서 공통으로 쓰이는 역할과
      테넌트 내부에서만 의미 있는 역할을 구분하고 있는가?
    • PLATFORM_ADMINTENANT_ADMIN을 혼동하지 않도록
      스코프/이름 규칙이 명확한가?
  2. 역할 전략

    • 플랫폼에서 정한 고정 역할 세트만 제공할 것인가?

    • 테넌트별 커스텀 역할이 필요한 수준의 고객/요구가 있는가?

    • 커스텀 역할이 필요하다면,

      • UI/운영 난이도,
      • 디버깅 난이도,
      • 권한 설명/문서화까지 고려했는가?
  3. permission 설계

    • 권한을 API 단위까지 세밀하게 쪼갰는가,
    • 아니면 기능 묶음 단위까지만 정의했는가?
    • 지나치게 세밀해서 운영이 어려워지지 않을 정도의 수준인가?
  4. 권한 체크 위치

    • 프레임워크 레벨에서 1차 필터링이 되고 있는가?
    • 도메인 레벨에서 “자기 소유 데이터만 접근” 같은 조건까지 체크하고 있는가?
    • 권한 체크가 빠질 수 있는 경로(직접 Repository 접근 등)는 없는가?
  5. JWT와의 연결

    • 토큰에 tenant_id, tenant_user_id, roles는 어느 수준까지 넣을 것인가?
    • permission을 토큰에 넣을지, 서버에서 조회할지 결정했는가?
    • 역할/권한이 바뀌었을 때 토큰 갱신/무효화 전략은 무엇인가?

✅ 마무리

3편에서는 멀티테넌트 회원·인증 구조 위에 얹는 RBAC를 정리했다.

요약하면:

  • tenant / user / tenant_user 위에
    role / permission / role_permission / tenant_user_role을 얹어서

    • 플랫폼 전체 역할,
    • 테넌트 내부 역할을 분리하고,
  • 역할 전략(고정 vs 커스텀), permission granularity,
    권한 체크 위치(프레임워크 vs 도메인),
    JWT와의 연결 방식 등을 상황에 맞게 선택해야 한다.

멀티테넌트 RBAC는
“정답”이라기보다 트레이드오프 싸움에 가깝다.

  • 초기에는 단순한 역할 세트로 시작해서,
  • 실제 고객 요구와 운영 경험이 쌓일수록
    권한 모델을 점진적으로 확장하는 전략도 충분히 엔지니어다운 선택이다.

다음 편에서는 이 RBAC 구조와 테넌트 모델을 전제로, B2B SaaS에서 테넌트별 스케일링·분리 전략(노이즈 네이버 대응, 테넌트별 Rate Limit, 클러스터 분리 등) 을 인프라 관점에서 정리해볼 예정이다.


참고 문헌

  1. NIST CSRC, “Role Based Access Control (RBAC)”.

  2. Sandhu et al., “The NIST Model for Role-Based Access Control”.

  3. Ferraiolo et al., “Proposed NIST Standard for Role Based Access Control”.

  4. IBM, “What Is Role-Based Access Control (RBAC)?”.

  5. NIST, “A Role-Based Access Control Model and Reference Implementation”.

profile
하나씩 꽉꽉 눌러 담는 실무 개발 노트 ✍️📦

0개의 댓글