Case Study · 11

NMS-Web RBAC 권한 시스템 — 8 리소스 × 6 액션을 단일 prop으로 통합

다양한 역할이 8개 도메인 리소스에 대해 서로 다른 권한을 갖는 NMS-Web에, 토큰 보유 전략 · 권한 파생 · UI 게이트 · 라우트 가드 · 메뉴 가시성까지 한 줄기로 묶는 다층 권한 아키텍처를 적용했습니다. 단일 게이트 컴포넌트의 prop 1세트가 인라인 게이트 · 라우트 가드 · 메뉴 3 사용처를 모두 처리합니다.

Product
NMS (Web)
Role
Frontend Engineer
Period
2025.05 — 2025.10
Stack
Next.js 14 App Router React 18 TypeScript 5 Context API + ref-based state
Implementation AI-paired (Claude agent)
  • 리소스 8종(post·user·node·group·branch·report·alarm·client) × 액션 6종(create·read·update·delete·message·manage)을 단일 상수로 표준화.
  • 권한 레벨 4단계(NONE/READ/WRITE/ADMIN)를 액션 집합에서 자동 파생 — `manage|delete`→ADMIN, `update|create`→WRITE.
  • 토큰을 ref로 보유 + 15분 인터벌 사일런트 리프레시. 토큰 갱신 시 트리 리렌더 0회.
  • 게이트 prop 1세트(`resource`/`action`/`permissions`/`requireAll`/`minLevel`/`customCheck`)로 인라인 게이트 · 라우트 가드 · 메뉴 3 사용처 통합.
  • 헤더 서브메뉴 항목이 `(roleInfo) => boolean` 콜백으로 권한별 메뉴 숨김 — 권한 누락 시 진입로 자체 제거.

배경

NMS-Web은 관리자 · 매니저 · 사용자 · 뷰어 · 커스텀 등 다양한 역할이 8개 도메인 리소스에 대해 조회 · 작성 · 수정 · 삭제 · 메시지 · 관리 권한을 서로 다르게 갖는 B2B 제품입니다. 권한 체크가 페이지 진입 · 메뉴 노출 · 버튼 활성화 · API 호출 전반에 흩어지면 권한 누락과 보안 사고 위험이 산술적으로 누적됩니다.

초기 빌드에는 권한 분기를 컴포넌트마다 직접 작성하던 흔적이 있었고, 페이지가 늘어날수록 어디서 한 곳이 빠지는지 추적이 어려워지는 구조였습니다.

접근

토큰 보유 전략부터 다시 짰습니다. 토큰을 state가 아닌 ref에 저장해 트리 리렌더를 분리하고, getter 함수로 노출했습니다. 15분 인터벌로 사일런트 리프레시를 돌려 사용자 인터랙션 없이도 세션이 유지되도록 했습니다.

권한 정보는 roleInforesource → actions[] 형태로 한 번만 가공해두고, 그 위에 단일 권한 · 다중 권한 · 권한 레벨 · 커스텀 콜백을 모두 받는 게이트 컴포넌트를 만들었습니다. 같은 props 표면을 페이지 단위 라우트 가드와 헤더 서브메뉴의 accessCheck 콜백에서 재사용했습니다.

한 컴포넌트가 게이트 · 라우트 · 메뉴를 모두 처리하면 권한 정책이 한 곳에서 변경되고 전 화면에 즉시 전파됩니다.

권한 레벨은 액션 집합에서 자동 파생되도록 설계했습니다 — managedelete를 가지면 ADMIN, updatecreate면 WRITE, read만 있으면 READ, 그 외 NONE. 호출부는 의도에 따라 리소스 × 액션 명세 또는 권한 레벨 명세를 선택할 수 있습니다 — 같은 정책을 두 개의 표현으로 노출해 호출부 가독성과 정확성 둘 다를 얻었습니다.

결과

  • 리소스 8종 × 액션 6종을 단일 enum/const로 표준화.
  • 권한 레벨 4단계 (NONE/READ/WRITE/ADMIN)로 자동 파생.
  • 토큰 리프레시 15분 인터벌 + ref 보유 — 토큰 갱신 시 트리 리렌더 0회.
  • 게이트 prop 1세트로 3 사용처 (인라인 게이트 / 라우트 가드 / 메뉴 가시성) 통합.
  • 서브메뉴 항목이 콜백으로 권한별 메뉴 숨김 — 권한 누락 시 진입로 자체 제거.

배운 점

권한 같은 횡단 관심사는 한 곳에서 정책을 결정하고 여러 곳에서 같은 prop 표면으로 적용하는 게 가장 안전한 구조였습니다. UI 컴포넌트별로 권한 분기를 직접 쓰면 페이지가 늘어날 때마다 누락 위험이 산술적으로 증가하지만, 단일 게이트로 통합하면 정책 변경 1회 = 전 화면 즉시 반영이 됩니다.

또 한 가지: 토큰을 ref로 보유한다는 작은 결정이 리렌더 폭주를 차단했습니다. 인증 갱신 같은 cross-cutting 상태는 트리 렌더 사이클에서 분리되어야 한다는 게 이 작업의 가장 실용적 교훈이었습니다.