RBAC 도입을 통한 권한 관리 로직 개선기

이지호·2025년 1월 13일
4
post-thumbnail

1. 기존의 권한 관리 로직의 아쉬운 점

Ask-It 프로젝트는 실시간 Q&A 서비스로, 하나의 세션 안에서 여러 사용자가 질문과 답변을 주고받을 수 있는 기능을 제공했다.

이때 중요한 이슈 중 하나는 권한 관리였다. 누구나 질문을 올릴 순 있지만, 모든 유저가 동일하게 세션을 종료하거나 다른 사람을 호스트로 지정할 수 있다면 혼란이 생길 수 있기 때문이다.

ask-it 권한관리

최초 구현에서는 다음과 같이 슈퍼 호스트(세션 생성자), 서브 호스트, 참가자 세 가지 역할을 두고, 이 역할에 따라 권한을 달리 부여했다.

권한슈퍼 호스트서브 호스트참가자
1. 세션 종료OXX
2. 타인에게 호스트 권한 부여OXX
3. 타인의 호스트 권한 해제OXX
4. 질문 삭제OOX
5. 답변 삭제OOX
6. 질문 고정OOX
7. 질문에 대한 답변 완료 기능OOX

이러한 로직은 if문으로 '역할'을 판단하여 처리되고 있었다.

기존에는 두 개의 테이블에서 각각 다른 방식으로 권한을 확인했다:

  • 슈퍼 호스트(세션 생성자) 의 경우 Session 테이블의 createUserId 컬럼과 현재 사용자의 ID를 비교하여 확인했다:
    // 예시 코드
    if (session.createUserId === currentUserId) {
      // 슈퍼 호스트임
    }
  • 서브 호스트 의 경우 UserSessionToken 테이블의 isHost 컬럼으로 확인했다:
    // 예시 코드
    if (userSessionToken.isHost) {
      // 서브 호스트임
    }

그리고 다음과 같이 역할을 기반으로 권한을 확인했다:

async deleteQuestion(questionId: number, question: Question, { token }: BaseDto) { 
const { isHost } = await this.sessionAuthRepository.findByToken(token);

if(!isHost)
    throw new ForbiddenException('권한이 없습니다.');

//슈퍼 호스트이거나 서브 호스트인 경우 삭제 권한 부여

하지만 이런 방식은 새로운 역할이 추가되거나 권한 범위가 변경될 때마다 코드를 수정해야 하는 단점이 있었다.

이를테면 위와 같은 코드에서 질문 삭제 권한을 서브 호스트에게서는 박탈한다고 한다면, 또 분기 처리가 생겨야할 것이다.

async deleteQuestion(questionId: number, question: Question, { token }: BaseDto) { 
const { isHost } = await this.sessionAuthRepository.findByToken(token);

if(!isHost)
    throw new ForbiddenException('권한이 없습니다.');
if(/*서브 호스트인 경우*/)
    throw new ForbiddenException('권한이 없습니다.');

요구사항이 추가, 삭제될수록 권한 로직으로 인해 서비스 레이어가 복잡해진다는 문제가 있다. 이는 코드의 유지보수성을 떨어뜨리고, 새로운 기능 추가 시 개발자가 고려해야 할 사항을 증가시키는 원인이 되었다. 이러한 문제를 해결하기 위해 우리는 새로운 접근 방식이 필요했다.

2. RBAC가 뭔데?

역할 기반 권한 관리(Role Based Access Control, RBAC)는 말 그대로 “역할(Role)을 기반으로 권한(Permission)을 관리”하는 방법이다.

사실 처음부터 RBAC라는 용어를 알고 있었던 것은 아니었다.

우연히 DB의 권한 관리 로직을 공부하고 있었는데, 역할과 권한을 분리해두고, 사용자가 어떤 역할을 맡았는지에 따라 권한을 부여하는 방식이 인상적이었다.

예를 들어 MySQL에서 다음과 같이 “읽기 전용”, “쓰기 가능” 등의 역할을 생성하고, 해당 역할에 필요한 권한을 묶어둘 수 있다.

-- 역할 생성
CREATE ROLE 'role_emp_read', 'role_emp_write';

-- 역할에 권한 부여
GRANT SELECT ON employees.* TO 'role_emp_read';
GRANT INSERT, UPDATE, DELETE ON employees.* TO 'role_emp_write';

-- 사용자 생성
CREATE USER 'reader'@'127.0.0.1' IDENTIFIED BY 'qwerty';
CREATE USER 'writer'@'127.0.0.1' IDENTIFIED BY 'qwerty';

-- 사용자에게 역할 부여
GRANT 'role_emp_read' TO 'reader'@'127.0.0.1';
GRANT 'role_emp_read', 'role_emp_write' TO 'writer'@'127.0.0.1';

이는 “역할”에 권한을 묶어두고, “사용자”는 역할만 부여받으면 자동으로 그 역할이 가진 권한들까지 받아오는 구조다. 이런 방식은 새로운 권한을 추가하거나 제거할 때도 코드나 쿼리를 여러 곳 수정할 필요 없이, 역할-권한 매핑만 변경하면 되는 장점이 있다.

3. 권한 관리 로직 개선하기

3.1 Role과 Permission

Ask-It 프로젝트에도 RBAC 개념을 적용하기 위해, 각 역할(Role)과 권한(Permission), 그리고 둘 사이를 연결해주는 RolePermission 테이블을 새로 정의했다.

ERD

이렇게 역할-권한 매핑을 더 세밀하게 관리할 수 있게 되었다. 예를 들어, 서브 호스트에게 질문 삭제 권한은 없지만 답변 삭제 권한은 주고 싶다면, 해당 역할의 권한 목록에서 DELETE_QUESTION 권한만 제외하면 된다. 또한 새로운 역할이 필요할 때도 기존 권한들을 조합해 손쉽게 새로운 역할을 만들 수 있다. 이는 권한 관리의 유연성을 크게 향상시켰다.

3.2 서비스 로직

권한 체크 로직은 다음과 같이 개선했다.

async deleteQuestion(questionId: number, question: Question, { token }: BaseDto) {
  const { role } = await this.sessionAuthRepository.findByTokenWithPermissions(token);
  const granted = role.permissions.some(
    ({ permissionId }) => permissionId === Permissions.DELETE_QUESTION,
  );

  if (!granted) 
	throw new ForbiddenException('권한이 없습니다.');
  // 이하 생략...
}

여기서 findByTokenWithPermissions 메서드는 UserSessionToken을 조회한 후, 그에 연결된 Role → Permission 목록을 가져온다. 그리고 나서 “이 사용자가 DELETE_QUESTION 권한을 가지고 있는지” 여부를 확인해 권한을 부여한다.

이렇게 하면, “서브 호스트”에게서 질문 삭제 권한을 빼고 싶을 때 굳이 코드를 수정하지 않아도 된다. DB에서 “서브 호스트”의 RolePermission 레코드 중 DELETE_QUESTION를 제거하기만 하면 끝난다. 권한 확장도 마찬가지 방식으로 손쉽게 할 수 있다.

4. 마치며

이번 리팩토링을 통해 MySQL의 권한 관리 시스템에서 영감을 받아 RBAC를 도입해보았다. 실제로 AWS IAM도 이와 유사한 방식으로 사용자, 그룹, 역할, 정책을 분리하여 권한을 관리하는 것으로 알고 있다. 확실히 이 방식이 권한 관리가 복잡해질수록 유용한 방법이라는 생각이 들었다.

처음에는 단순한 조건문으로 시작했던 권한 체크 로직이 이제는 더 유연하고 확장 가능한 시스템으로 발전한 것 같아 뿌듯하다. 특히 새로운 역할이나 권한이 추가될 때마다 코드를 수정하지 않아도 된다는 점이 가장 큰 성과라고 생각한다. 앞으로 서비스가 성장하면서 더 복잡한 권한 구조가 필요해지더라도, 이 기반 위에서 충분히 대응할 수 있을 것 같다.

이번 리팩토링을 통해 배운 점은 '확장 가능한 설계의 중요성'이다. 초기에는 단순한 구현으로 시작하더라도, 서비스의 성장을 고려한 유연한 설계가 필요하다는 것을 깨달았다.

0개의 댓글

관련 채용 정보