Ari.dev

화면마다 권한 체크를 심으면 결국 하나는 빠뜨린다 — 5단계 운영자 권한을 라우팅 단위로 옮긴 이야기

운영자 권한이 5단계로 나뉜 어드민에서, 컴포넌트마다 권한 분기를 심는 방식은 화면이 늘어날수록 누락 위험이 커졌다. 권한 판정을 화면이 아니라 라우팅 단위로 옮겨 진입 시점에 한 번만 확인하도록 재정의한 과정.

아키텍처

관련 프로젝트: 운영 어드민 단독 구축

운영 어드민은 접근 범위가 다른 5단계 운영자 권한을 다뤘다. 문제는 "권한이 5단계다"가 아니라, 그 권한 체크를 어디에 심을 것인가였다.

문제 — 컴포넌트마다 심은 권한 체크는 누락된다

처음엔 자연스럽게 화면(컴포넌트) 안에서 권한을 체크했다. "이 버튼은 상위 권한만", "이 메뉴는 이 권한부터" 같은 조건을 컴포넌트 렌더 로직에 끼워 넣는 방식이다.

당장은 동작한다. 하지만 화면이 30개, 50개로 늘어나면서 이 방식의 진짜 비용이 드러났다.

  • 새 화면을 추가할 때 권한 체크를 빠뜨려도 아무도 알아채지 못한다. 컴파일도, 실행도 정상이다. 그냥 "권한 없는 사용자도 들어가지는" 화면이 하나 생길 뿐이다.
  • 어떤 화면에 어떤 권한이 필요한지 한눈에 안 보인다. 확인하려면 컴포넌트 트리를 하나씩 열어봐야 한다.
  • 같은 권한 조건이 여러 화면에 중복된다. 조건이 바뀌면 그 조건이 박힌 모든 컴포넌트를 찾아 고쳐야 한다.

즉 권한 체크가 "화면을 만들 때 각자 알아서 챙겨야 하는 일"이 되어 있었다. 사람이 매번 기억해야 하는 규칙은 언젠가 빠진다.

접근 — 판정을 화면에서 라우팅으로 옮긴다

바꾼 지점은 하나다. 권한 판정을 컴포넌트가 아니라 라우트 진입 시점 하나로 모은다.

// 개념 스케치 — 라우트 정의에 권한 레벨을 선언하는 구조 (실제 구현 아님)
const routes = [
  { path: "/facility", element: <FacilityPage />, minRole: 2 },
  { path: "/facility/policy", element: <FacilityPolicyPage />, minRole: 3 },
  { path: "/settings/admin", element: <AdminSettingsPage />, minRole: 5 },
];

function RequireRole({ minRole, children }) {
  const role = useCurrentOperatorRole();
  if (role < minRole) return <Navigate to="/forbidden" />;
  return children;
}

화면을 추가하는 사람은 라우트 설정에 minRole 한 줄만 채우면 된다. 컴포넌트 내부에는 권한 분기가 아예 없다 — 권한을 "깜빡할 자리" 자체가 사라진다. 라우트 설정 파일 하나만 보면 전체 권한 맵이 한눈에 들어오는 것도 부가 효과였다.

왜 컴포넌트 단위가 아니라 라우팅 단위인가

  • 진입점이 하나다. 화면으로 들어가는 경로는 라우팅을 반드시 거친다. 판정을 거기 두면 우회할 방법이 없다.
  • 선언과 구현이 분리된다. "이 화면은 몇 단계부터 접근 가능한가"는 데이터(라우트 설정)이지, 로직이 아니다. 데이터는 누락 여부를 코드 리뷰에서 바로 눈으로 확인할 수 있다.
  • 컴포넌트가 권한을 몰라도 된다. 화면 컴포넌트는 "나는 어떤 권한부터 보이는가"를 신경 쓸 필요가 없어졌다. 관심사가 명확히 갈린다.

정리

구분before(컴포넌트 단위 체크)after(라우팅 단위 판정)
권한 조건 위치화면마다 흩어져 중복라우트 설정 한 곳에 선언
새 화면 추가 시체크를 빠뜨려도 조용히 통과minRole 미선언 시 라우트 자체가 안 보임
전체 권한 맵 파악컴포넌트 트리를 일일이 확인라우트 설정 파일 하나로 조망
조건 변경 시박혀 있는 모든 컴포넌트를 찾아 수정라우트 설정 한 줄만 수정

권한 체크는 "빠짐없이 챙기는 성실함"으로 지켜지지 않는다. 화면이 늘어날수록 사람이 매번 기억해야 하는 규칙은 반드시 하나씩 샌다. 판정을 사람이 반복해야 하는 자리에서, 반드시 거치게 되는 자리로 옮기는 것이 이 문제의 실제 해법이었다.

댓글