애플리케이션을 안전하게 유지하려면 누가, 무엇에 접근할 수 있는지 통제해야 합니다. 접근 제어는 크게 두 가지로 나눌 수 있습니다. '누구'를 허용할지 결정하는 인증(Authentication)과, 사용자가 '무엇'에 접근할 수 있는지 결정하는 인가(Authorization)입니다.
인증은 다음 기회에 다루기로 하고, 이번 글에서는 사용자 인가에 집중해 보겠습니다. 인가를 구현하는 방법은 일반적으로 두 가지가 있습니다. 역할 기반(Role-based) 전략 또는 리소스 기반(Resource-based) 전략을 사용하는 것입니다.
이 시리즈(2부작)에서는 Ruby on Rails 블로그 애플리케이션에 액션 폴리시(Action Policy) 젬을 활용하는 방법을 심도 있게 살펴봅니다.
이번 1부에서는 액션 폴리시의 기본 개념부터 다뤄보겠습니다.
그럼 시작해 볼까요?
사전 준비 사항
- Ruby (본 글에서는 3.2.2 버전 사용)
- Rails (7.0.7 버전 사용)
- Ruby 사용 경험
먼저 리소스 기반 인가가 무엇인지 정의하면서 본격적으로 들어가 보겠습니다.
리소스 기반 인가란 무엇인가?
역할 기반 인가가 사전에 정의된 사용자 역할에 따라 권한을 설정하는 방식이라면, 리소스 기반 인가는 애플리케이션 내부의 실제 리소스 자체에 규칙을 설정하여 접근을 통제합니다. 각 리소스에는 해당 리소스에서 사용자가 무엇을 할 수 있는지 명확히 정의한 정책(Policy)이 연결됩니다.
이 글은 리소스 기반 인가에 초점을 맞추고 있지만, 두 가지 인가 전략의 차이점을 이해하고 있으면 각 방식이 어떤 상황에 적합한지 판단하는 데 큰 도움이 됩니다.
역할 기반 인가를 사용해야 할 때
다음과 같은 경우 역할 기반 인가가 적합합니다.
- 단순한 애플리케이션 - 권한 체계가 단순하고 사용자 역할이 적은 앱이라면 역할 기반 인가 전략만으로 충분할 수 있습니다.
- 명확히 구분되는 사용자 그룹 - '관리자(admins)', '편집자(editors)', '작성자(writers)'처럼 잘 정의된 사용자 그룹이 있다면, 사용자 역할 수준에서 접근 제어를 처리하는 역할 기반 인가가 효과적입니다.
리소스 기반 인가를 사용해야 할 때
리소스 기반 인가는 다음과 같은 경우 빛을 발합니다.
- 동적이거나 복잡한 접근 제어 - 애플리케이션의 인가 요구 사항이 자주 변경되거나 동적인 조건에 따라 결정된다면 리소스 기반 인가 시스템이 더 나은 선택입니다.
- 세밀한(Fine-grained) 제어 - 여러 조건에 따라 리소스 접근을 허용하거나 거부해야 할 때 유용합니다. 예를 들어, 사용자가 동적 카테고리 목록을 기반으로 지원 티켓을 제출하는 Rails 헬프데스크 앱을 생각해 보세요. 지원 담당자에게 카테고리별로 티켓이 배정되는 상황이라면 리소스 기반 인가가 진가를 발휘합니다.
- 객체 지향적 제어 - 리소스 기반 인가는 객체 수준에서 동작하기 때문에, 복잡한 객체 지향 규칙을 정의하기가 훨씬 수월합니다.
결국 역할 기반과 리소스 기반 중 무엇을 선택할지는 애플리케이션 고유의 특성에 따라 달라집니다.
이제 액션 폴리시 젬에 대해 알아보겠습니다.
Ruby와 Rails를 위한 액션 폴리시 젬
액션 폴리시는 Ruby 및 Rails 앱을 위한 유연하고 확장 가능하며 고성능의 인가 프레임워크입니다. 다양한 캐싱 전략을 기본적으로 지원하기 때문에, 특히 인가 규칙에 데이터베이스 쿼리가 필요한 경우 매우 빠른 성능을 보여줍니다.
이 젬이 리소스 기반 규칙 구축에 이상적인 또 다른 이유는 높은 커스터마이징 자유도입니다. 여러 Ruby 클래스와 모듈을 다양한 방식으로 조합할 수 있어, Rails 컨트롤러를 넘어 앱의 어디에서든 세밀한 접근 제어 규칙을 설정할 수 있습니다.
더 자세한 내용은 액션 폴리시 프로젝트 홈페이지를 참고하세요.
이제 오늘 만들어 볼 Rails 앱을 살펴보겠습니다.
Rails 앱 생성하기
앞으로 우리는 사용자가 게시글(Post)을 생성(Create), 조회(Read), 수정(Update), 삭제(Delete)할 수 있는 Rails 7 블로그 애플리케이션을 예제로 사용합니다.
Post 모델에 대해 리소스 기반 접근 제어를 제공하는 액션 폴리시를 단계적으로 정의해 보겠습니다. 액션 폴리시를 활용해 이 접근 제어 전략을 실제로 구현하는 방법을 배우게 될 것입니다.
먼저 새 Rails 애플리케이션을 생성합니다:
참고: 예제 앱에는 Pico CSS 스타일을 사용하지만, 원하는 스타일을 자유롭게 사용하셔도 됩니다.
다음으로, 튜토리얼 전반에서 사용할 Post 리소스를 스캐폴딩(scaffold)으로 빠르게 생성합니다:
그리고 rails db:migrate 명령으로 마이그레이션을 실행합니다.
사용자 인증 설정하기
이 글의 주제는 사용자 인가이지만, 반드시 짚고 넘어가야 할 것이 있습니다. 바로 사용자 인증입니다. 인증이 없다면 이후에 정의하려는 모든 인가 정책은 무용지물이 됩니다. 다행히 인증을 처음부터 직접 작성할 필요는 없습니다. Devise를 활용해 보겠습니다.
Devise 설치하기
Gemfile에 Devise 젬을 추가하는 것부터 시작합니다:
그런 다음 설치하고 User 모델을 생성합니다:
또한 앱의 개발 환경 설정 파일에 config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }를 추가하는 것도 잊지 마세요.
다음으로, Post 리소스에 대한 다양한 사용자 접근 시나리오를 테스트할 수 있도록 몇 가지 기본 사용자 역할을 구현하겠습니다.
기본 사용자 역할 정의하기
먼저 users 테이블에 role 컬럼을 추가합니다. 아래 명령으로 마이그레이션을 생성하세요:
참고: role 컬럼에 정수(integer) 타입을 사용하면 enum을 활용할 수 있습니다. enum은 역할을 빠르고 간편하게 구현할 수 있는 방법입니다.
rails db:migrate로 마이그레이션을 실행한 후, User 모델을 열어 다음과 같이 수정합니다:
여기까지 완료했다면, 이제 액션 폴리시 설치로 넘어가겠습니다.
Ruby와 Rails를 위한 액션 폴리시 설정하기
앱의 Gemfile을 열고 아래 코드를 추가합니다:
그리고 bundle install 명령을 실행해 액션 폴리시를 설치합니다.
마지막으로 아래 명령으로 설치를 완료합니다:
이렇게 하면 app/policies 디렉터리 아래에 기반이 되는 ApplicationPolicy 클래스가 생성됩니다:
액션 폴리시 기본 사용법
액션 폴리시의 핵심 기반은 ApplicationPolicy라는 정책(policy) 클래스입니다. 이곳에서 전역 설정을 정의하면 모든 정책이 이를 상속받습니다. 모든 정책은 app/policies 디렉터리 아래에 위치시키고, 앱의 리소스별로 분리하는 것이 좋습니다. 예를 들어 Post 리소스가 있다면 대응하는 정책은 PostPolicy, Comment 리소스가 있다면 CommentPolicy가 되는 식입니다.
액션 폴리시를 사용할 때 한 가지 더 주의할 점이 있습니다. 규칙 정의는 반드시 정책 클래스의 public 메서드 내에서 이루어져야 합니다. private 메서드에서 정의하면 에러가 발생합니다.
이 내용을 바탕으로, 다양한 수준의 사용자 접근 권한을 단계별로 구축해 나갈 PostPolicy를 만들어 보겠습니다.
첫 번째 정책 만들기
app/policies 디렉터리 아래에 post_policy.rb라는 새 파일을 생성합니다:
여기서 Post 리소스에 간단한 규칙 하나를 정의했습니다. 누구든 Post에 접근할 수 있다는 규칙입니다.
게시글과 사용자 연결하기
새로 만든 게시글 정책을 실제로 활용하려면, 앞서 생성한 Post 리소스를 수정해서 게시글이 생성될 때 로그인한 사용자와 연결되도록 해야 합니다(posts 테이블에 user_id 컬럼 추가):
그리고 rails db:migrate로 마이그레이션을 실행합니다.
다음으로 이 변경 사항을 반영하도록 posts 컨트롤러를 수정해야 합니다. 먼저 허용되는 post_params에 user_id를 추가합니다:
그다음 create 액션을 수정합니다:
이제 PostsController를 보호해 보겠습니다.
컨트롤러에 인가 구현하기
PostsController에 콜백을 추가하여 로그인한 사용자만 접근할 수 있도록 수정합니다:
다음으로 새로 만든 Post 정책을 활용해 기본적인 사용자 접근 제어를 추가합니다:
여기서 posts 컨트롤러의 update와 destroy 액션에 접근 규칙을 정의했습니다. 게시글의 작성자(또는 'author' 역할을 가진 사용자)는 게시글을 수정할 수 있지만, 게시글 삭제는 오직 해당 게시글의 작성자만 가능하도록 설정한 것입니다.
꿀팁: 액션 폴리시는 Devise가 제공하는 current_user를 참조하여 정책 내부의 user에 자동으로 할당할 수 있습니다.
다음으로, posts 컨트롤러에서 이 접근 규칙을 실제로 사용하도록 합니다:
posts 컨트롤러에 접근 규칙을 적용했다면, 다음 단계는 뷰(view)까지 보호하는 것입니다.
인가로 뷰 보호하기
아래 스크린샷에서는 'reader' 역할을 가진 사용자(user2@example.com)가 로그인한 상태입니다. 보시다시피 이 사용자는 user@example.com이 작성한 게시글을 조회할 수 있을 뿐만 아니라, 해당 게시글의 수정 및 삭제 링크까지 접근할 수 있습니다. 이것은 바람직하지 않습니다. 수정과 삭제 링크는 오직 게시글 작성자에게만 노출되어야 합니다.

첫 번째 과제는 게시글의 작성자가 아닌 사용자에게 이 링크들이 보이지 않도록 하는 것입니다. 이미 접근 규칙을 정의해 두었으므로, 액션 폴리시의 편리한 allowed_to? 메서드를 활용해 show 뷰에 적용하기만 하면 됩니다:
이 규칙을 적용한 후, 게시글의 show 페이지를 새로고침하면 어떻게 되는지 확인해 보겠습니다:

이전에는 접근할 수 있었던 같은 사용자가, 이제는 게시글 뷰에서 할 수 있는 작업이 제한된 것을 확인할 수 있습니다.
마치며
2부작 시리즈의 첫 번째 파트에서는 인가 젬인 액션 폴리시의 기본 개념들을 학습했습니다.
두 번째이자 마지막 파트에서는 좀 더 고급 활용 사례를 살펴보겠습니다.
즐거운 코딩 되세요!
P.S. Ruby Magic의 글을 발행 즉시 읽어보고 싶다면 Ruby Magic 뉴스레터를 구독하고 어떤 글도 놓치지 마세요!