← 모든 글

RBAC 를 코드에 박지 않는 이유

역할 기반 접근 제어를 하드코딩하지 않고 데이터로 관리하는 방식을 선택한 이유와 구현 방향, 그리고 실제 운영에서 생기는 트레이드오프를 이야기한다.

처음에는 코드에 박았다

초기에는 권한 체크를 코드에 직접 넣었다. if (user.role === 'ADMIN') 같은 패턴이 여러 곳에 흩어지기 시작했다. 처음에는 단순하고 빠른 것처럼 보였다.

문제가 드러난 것은 역할이 하나 더 추가됐을 때였다. 새 역할을 위해 수정해야 할 곳이 코드베이스 전체에 퍼져 있었다. 누락이 생기면 권한이 의도치 않게 열리거나 막혔다. 버그를 추적하기도 어려웠다.

권한을 데이터로 관리한다는 것

우리가 채택한 방식은 권한 정의를 DB 테이블에서 관리하는 것이다. 역할, 리소스, 액션의 조합으로 허용 여부를 저장하고, 코드는 이 테이블을 조회해서 판단한다.

기본 구조는 다음과 같다.

  • roles 테이블: 역할 정의
  • permissions 테이블: (role_id, resource, action) 조합으로 허용 여부 저장
  • 서비스 레이어에서 hasPermission(userId, resource, action)을 호출해 권한 확인

이렇게 하면 새 역할을 추가하거나, 기존 역할의 권한 범위를 바꾸는 것이 코드 배포 없이 DB 변경만으로 가능하다.

코드에 남기는 것과 남기지 않는 것

모든 것을 데이터로 빼는 것이 맞는 것은 아니다. 우리가 여전히 코드에 남기는 것이 있다.

인증(Authentication) 자체는 코드에서 다룬다. 로그인 여부, 토큰 유효성 검증은 미들웨어 레이어에서 처리한다.

특정 불변 규칙: 예를 들어 “자신의 계정 정보는 본인만 볼 수 있다”처럼 어떤 역할이더라도 바뀌어선 안 되는 규칙은 코드에 명시적으로 남긴다. 이것을 DB에서 실수로 바꿀 수 있게 두면 오히려 위험하다.

실제 운영에서 생기는 트레이드오프

성능: 매 요청마다 DB를 조회하면 지연이 생긴다. 이를 위해 권한 테이블은 캐시 레이어에 올려두고, 변경이 발생할 때만 캐시를 무효화한다. TTL을 짧게 가져가는 것만으로도 대부분의 성능 문제는 해결됐다.

감사 로그: 권한 변경 이력을 남기는 것이 중요하다. 누가, 언제, 어떤 역할의 권한을 바꿨는지 기록이 없으면 문제가 생겼을 때 추적이 불가능하다.

테스트 복잡도: 코드에 박혀 있으면 유닛 테스트가 단순하다. 데이터로 관리하면 테스트 환경에서 권한 데이터를 함께 세팅해야 한다. 이 불편함을 감수할 만한 유연성이 실제로 있는지를 팀 상황에 맞게 판단해야 한다.

작은 팀에 적합한 수준

우리가 구현한 것은 엔터프라이즈 RBAC의 축소판이다. 계층적 역할 상속, 조건부 권한, 동적 속성 기반 접근 제어 같은 기능은 포함하지 않았다. 현재 요구사항을 충족하는 가장 단순한 형태를 유지하는 것이 우선이다.

권한 시스템은 한 번 구조를 잡으면 바꾸기 어렵다. 처음부터 코드에 박지 않는 것이, 나중에 유연하게 바꿀 수 있는 여지를 남기는 가장 현실적인 선택이었다.

— by slecs