웹 애플리케이션의 일반적인 요구 사항은 특정 역할 및 권한을 할당하는 기능입니다.
많은 유형의 웹 응용 프로그램은 제한된 액세스를 제공할 때 관리자와 일반 사용자를 구분합니다. 이는 사용자가 관리자인지 여부를 결정하는 간단한 부울을 사용하여 수행되는 경우가 많습니다. 그러나 역할과 권한은 훨씬 더 복잡해질 수 있습니다.
애플리케이션의 가치는 특정 데이터 및 작업에 대한 액세스를 제한하는 데 있습니다. 그것은 확실히 당신이 망치고 싶지 않은 것입니다. 이 게시물에서는 기본 Ruby on Rails 애플리케이션에서 역할과 권한을 구현하는 방법을 설명합니다.
권한을 관리하려면 gem이 필요합니까?
아니요, 특히 애플리케이션이 작고 코드 기반에 더 많은 종속성을 추가하지 않으려는 경우 gem이 필요하지 않습니다. 그러나 대안을 찾고 있다면 역할과 권한을 다루는 가장 인기 있는 gem이 있습니다. :
-
DeviseDevise는 인증 및 역할 관리를 위한 보석이며 정말 복잡하고 강력한 솔루션입니다. GitHub에 21.7k 별이 있는 이 게시물에서 가장 인기 있는 저장소이지만 역할 관리 이상의 기능을 수행합니다. 인증 솔루션으로 알려져 있으므로 매우 강력한 라이브러리가 필요한 경우에만 코드베이스에 적용하십시오.
-
Pundit:Pundit은 단순한 Ruby 객체를 사용하는 보석이며 아마도 우리가 다룰 가장 간단한 정책 보석일 것입니다. 사용이 간편하고 최소한의 권한만 부여되며 순수한 Ruby를 사용하는 것과 유사합니다. GitHub에서 별 730만 개를 획득했으며 현재 가장 인기 있는 정책 보석입니다.
-
CanCan:CanCan은 지정된 사용자가 액세스할 수 있는 리소스를 제한하는 권한 부여 라이브러리입니다. 그러나 CanCan은 수년간 사용이 중단되었으며 Rails 3 및 이전 릴리스에서만 작동합니다.
-
CanCanCan:CanCanCan은 Ruby 및 Ruby on Rails를 위한 또 다른 인증 라이브러리입니다. CanCan의 대안이며 현재 유지 관리 중입니다. GitHub에서 별 4.9k로 인기가 가장 낮지만 꽤 잘 작동하고 잘 유지되고 있습니다.
이 모든 보석은 훌륭하지만 일반 Ruby에서 직접 권한을 구축하는 것은 그리 어렵지 않습니다. 정책 개체 패턴이라는 전략을 사용하여 gem 없이 권한을 관리하는 방법을 보여 드리겠습니다.
정책 개체 패턴
정책 개체는 권한 및 역할을 처리하는 데 사용되는 디자인 패턴입니다. 무언가 또는 누군가가 작업을 수행할 수 있는지 여부를 확인해야 할 때마다 사용할 수 있습니다. 복잡한 비즈니스 규칙을 캡슐화하고 규칙이 다른 다른 정책 개체로 쉽게 대체할 수 있습니다. 모든 외부 종속성이 정책 개체에 주입되어 권한 확인 논리를 캡슐화하여 깨끗한 컨트롤러와 모델을 생성합니다. Pundit, Cancan 및 Cancancan과 같은 보석이 이 패턴을 구현합니다.
순수 정책 개체 규칙
- 반환은 부울 값이어야 합니다.
- 논리는 단순해야 합니다.
- 메소드 내부에서는 전달된 객체에 대해서만 메서드를 호출해야 합니다.
구현
명명 규칙부터 시작하겠습니다. 파일 이름에 _policy
가 있습니다. 접미사가 적용되고 끝에 클래스와 정책이 적용됩니다. 이 방법에서 이름은 항상 ?
로 끝납니다. 문자(예:UsersPolicy#allowed?
).
다음은 몇 가지 예시 코드입니다:
class UsersPolicy
def initialize(user)
@user = user
end
def allowed?
admin? || editor?
end
def editor?
@user.where(editor: true)
end
def admin?
@user.where(admin: true)
end
end
어떤 시나리오에서 사용해야 하나요?
앱에 두 가지 이상의 유형의 제한된 액세스 및 제한된 작업이 있는 경우. 예를 들어 다음을 사용하여 게시물을 작성할 수 있습니다.
- 적어도 하나의 태그
- 관리자와 편집자만 만들 수 있는 제한 사항 및
- 편집자가 확인해야 하는 요구 사항입니다.
다음은 정책 개체가 없는 컨트롤러의 예입니다.
class PostsController < ApplicationController
def create
if @post.tag_ids.size > 0
&& (current_user.role == ‘admin’
|| (current_user.role == ‘editor’ && current_user.verified_email))
# create
end
end
end
위의 조건 검사는 길고 보기 흉하고 읽을 수 없기 때문에 정책 개체 패턴을 적용해야 합니다.
먼저 PostsCreationPolicy
를 생성해 보겠습니다. .
class PostsCreationPolicy
attr_reader :user, :post
def initialize(user, post)
@user = user
@post = post
end
def self.create?(user, post)
new(user, post).create?
end
def create?
with_tags? && author_is_allowed?
end
private
def with_tags?
post.tag_ids.size > 0
end
def author_is_allowed?
is_admin? || editor_is_verified?
end
def is_admin?
user.role == ‘admin’
end
def editor_is_verified?
user.role == ‘editor` && user.verified_email
end
end
정책 개체가 있는 컨트롤러는 다음과 같습니다.
class PostsController < ApplicationController
def create
if PostsCreationPolicy.create?(current_user, @post)
# create
end
end
end
Rails에서 정책 개체를 사용하는 방법
앱 /policies
내에 정책 디렉토리 생성 거기에 모든 정책 클래스를 배치합니다. 컨트롤러를 호출해야 하는 경우 작업에서 직접 호출하거나 before_action
을 사용할 수 있습니다. :
class PostsController < ApplicationController
before_action :authorized?, only: [:edit, :create, :update, :destroy]
def authorized?
unless ::PostsCreationPolicy.create?(current_user, @post)
render :file => "public/404.html", :status => :unauthorized
end
end
end
정책 개체를 테스트하는 방법
컨트롤러에서 동작을 테스트하는 것은 간단합니다.
require 'rails_helper'
RSpec.describe "/posts", type: :request do
describe "when user is not allowed" do
let(:user_not_allowed) { create(:user, admin: false, editor: false) }
let(:tag) { create(:tag) }
let(:valid_attributes) { attributes_for(:post, tag_id: tag.id) }
before do
sign_in user_not_allowed
end
describe "GET /index" do
it "return code 401" do
diet = Post.create! valid_attributes
get edit_post_url(post)
expect(response).to have_http_status(401)
end
end
end
end
정책 테스트도 간단합니다. 우리는 단 하나의 책임을 가진 많은 작은 방법을 가지고 있습니다.
require 'rails_helper'
RSpec.describe PostsCreationPolicy do
describe "when user is not allowed" do
let(:user) { create(:user, editor: false, admin: false) }
let(:user_editor) { create(:user, editor: true, email: verified) }
let(:tag) { create(:tag) }
let(:post) { create(:post, tag_id: tag.id) }
describe ".create?" do
context "when user is allowed" do
it "creates a new post" do
expect(described_class.create?(user_editor, post)).to eq(true)
end
end
context "when user is not allowed" do
it "does not create a new post" do
expected(described_class.create?(user, post)).to eq(false)
end
end
end
# ...more test cases
end
end
모든 시나리오에서 개체 생성이 허용되는지 테스트합니다.
결론
정책 패턴 개념은 작지만 큰 결과를 가져옵니다. 단순하거나 복잡한 권한을 처리해야 할 때마다 정책 개체를 적용하는 것을 고려하십시오. RSpec으로 테스트할 때 데이터베이스 레코드를 사용할 필요가 없습니다. 정책은 순전히 Ruby 개체이며 테스트는 간단하고 빠릅니다.