権限管理ですが、皆さんはどのように設計・実装していますか?
この記事では、架空の稼働管理システムを例に、権限管理の設計と実装について考えていきます。
権限管理とはあるユーザーがどのような操作を行えるかを制御する仕組みです。
例えば管理者は全ての操作を行える一方、一般ユーザーは閲覧のみ可能といった制御を行います。
結論からいうと、権限管理は次のようなデータ構造(テーブル設計)で設計・実装しておくと良いでしょう。
erDiagram users { int id PK string name } roles { int id PK string name } policies { int id PK string name } user_roles { int user_id FK int role_id FK } role_policies { int role_id FK int policy_id FK } users ||--o{ user_roles : has roles ||--o{ user_roles : has roles ||--o{ role_policies : has policies ||--o{ role_policies : has
roles
では管理者
や一般ユーザー
などの役割を定義します。
policies
では閲覧権限
や編集権限
などの具体的な権限を定義します。
ユーザーはuser_roles
で複数のロールを持つことができ、ロールはrole_policies
で複数のポリシーを持つことができます。
なぜこのような設計にするのかというと、以下の理由があります。
ロールベースの権限管理
ロールベースの権限管理を採用することで、ユーザーごとに個別に権限を設定するのではなく、役割ごとに権限をまとめて管理できます。
erDiagram users { int id PK string name } roles { int id PK string name } user_roles { int user_id FK int role_id FK } users ||--o{ user_roles : has roles ||--o{ user_roles : has
ただしポリシーが存在せず、ロールだけで権限を管理する場合は、ロールの権限を変更したい場合にソースコードの回収が必要になります。
たとえば次のようなコードを記事作成画面の権限チェックに使う場合を考えます。
ユーザーが管理者ロール、もしくは一般ユーザーロールを持っているかを確認するコードです。
いづれかのロールを持っている場合は、記事作成画面にアクセスできるようにします。
if user.has_role?(:admin, :user) # 記事作成画面を表示else # アクセス拒否end
ゲストユーザーも記事作成はできるようにしたい場合は、次のようにコードを変更する必要があります。
if user.has_role?(:admin, :user, :guest) # 記事作成画面を表示else # アクセス拒否end
管理者専用の記事作成画面を作成するので、通常の記事作成画面では管理者権限は不要にしたい場合は、次のようにコードを変更する必要があります。
if user.has_role?(:user, :guest) # 記事作成画面を表示else # アクセス拒否end
このように、ロールだけで権限を管理すると、権限の変更があるたびにソースコードを修正する必要が出てきます。
ポリシーによる権限管理
ポリシーを導入することで、権限の変更をソースコードの修正なしに行えるようになります。
ロールベースの権限管理と同じ、記事作成画面の権限チェックを考えます。
ポリシーベースでは次のように書きます。
if user.has_policy?(:create_article) # 記事作成画面を表示else # アクセス拒否end
erDiagram users { int id PK string name } policies { int id PK string name } user_policies { int user_id FK int policy_id FK } users ||--o{ user_policies : has policies ||--o{ user_policies : has
userがcreate_article
ポリシーを持っている場合は、記事作成画面にアクセスできるようになります。
しかし、ポリシーだけでロールが存在しない場合は、ユーザーごとにポリシーを設定する必要が出てきます。
ここでは記事作成ポリシーだけなので、問題を感じないかもしれませんが、
ユーザー作成権限、記事編集権限、記事削除権限などが増えて、
ユーザーも100人に増えたとしましょう。
3つのポリシーを100人のユーザーに設定するとなると、300個のポリシーを設定する必要が出てきます。
とても大変ですね…
ロールとポリシーの組み合わせ
そこでロールとポリシーを組み合わせて、
ロール自体に複数のポリシーを紐づけておきます。
そしてユーザーには複数のポリシーをもつ、ロールを複数持つことができるようにします。
そうすることでポリシーをユーザーごとに設定する必要がなくなります。
たとえば、管理者ロールには全てのポリシーを紐づけておきます。
一般ユーザーロールには記事作成ポリシー、閲覧ポリシーを紐づけておきます。
ゲストユーザーロールには閲覧ポリシーのみを紐づけておきます。
新しいポリシーが増えても、ロールに紐づけることで、ユーザーごとにポリシーを設定する必要がなくなります。
ソースコードに関しても次のようにポリシーベースで定義しておけば、
ロールにポリシーを紐づけるだけでソースコードの修正は不要になります。
たとえば記事作成画面のソースコードでは次のように定義しておきます。
ロールではなく、ポリシーをチェックするようにします。
if user.has_policy?(:create_article) # 記事作成画面を表示else # アクセス拒否end
この時点では、ゲストユーザーは記事作成ポリシーを持っていないので、記事作成画面にはアクセスできません。
要件の変更でゲストユーザーも記事作成できるようにしたい場合は、
ゲストユーザーロールに記事作成ポリシーを紐づけるだけで済みます。
ロールにポリシーを紐づける管理画面を用意しておけば、非エンジニアでも権限の変更が可能です。
ソースコードの修正は不要です。
まとめ
権限管理ははじめは簡単な要件でロールだけで管理したくなります。
しかし、要件が増えるとロールだけでは管理が難しくなります。
そこで、初めからロールとポリシーを組み合わせた権限管理の設計をしておくと、
要件の変更に柔軟に対応できるようになります。
今後皆さんが権限管理を設計・実装する際は、ロールとポリシーの組み合わせを検討してみてください。