バックエンドの認可について
どうもukmashiです。
年末にフロントエンドの認可について記事を書いたら、そこそこ読んでいただけたようで大変嬉しく思います。
FEに引き続き、BEでの認可について書いていこうと思います。
FEについて読んでいな方は読んでいただけると嬉しいです
概要
本稿は、バックエンドに関する認可に関する概念記事です。
DBレベルで解説するので、他の言語でも転用可能ですが、FW的に対応可能かはわかりません。
本稿では、認可を行う目的などお話しした後、実際のDB設計から、認可の実装概念を紹介していこうと思います。
簡単なSaaS製品で使えるくらいのそこそこスケールしやすい認可処理をを目標としています。
AWSのIAMくらい汎用性の高い認可は想定していません。
そこそこスケールしやすい認可処理の定義として
- とりあえず、 MVP開発としてRBACができれば十分だけど、今後、PBACにスケールしやすくしたい
- フロントエンドでPBACだから、PBACを採用したい(FE編でPBACを採用しているため)
とします。
なお、筆者の専門はRailsであるため、後半の実装の話では、Railsベースで話を進めさせていただきます。
認可について
まずは、認可を行う目的と、BEに認可を入れると何が嬉しいかを整理します。
認可を行う目的
FE編でも書いていますが、再掲します。
認可をすることで、適切なユーザーが、適切な処理を行うことが目的になります。
- Who is making the request?
誰がリクエストしているのか? - What are they trying to do?
何をしようとしているのか? - What are they doing it to?
何のために?
これを認可三大要素(Three main aspects of authorization)といい、認可をする上で、常に問い続ける内容です。
例えば、Postコントローラーへのアクションがあった場合
- Current Userが
- PostControllerのIndexにリクエストを
- Postの一覧を取得するために
参考:認可のアーキテクチャに関する考察(Authorization Academy IIを読んで)
もっと認可について、詳細を知りたい場合は、こちらがおすすめ
めっちゃわかりやすいです。
BEに認可を入れる目的
BEに認可を入れる目的は、セキュリティを担保することです
責務分離の観点から
- FEは描画に責任を持つ
- BEはデータ操作に責任を持つ
のが仕事です。
故に、ユーザーが誤ったデータ操作を行えないよう、行動に制限をかけることにBEは目的を持っています。
認可の種類
ここで、簡単に認可の種類について紹介します。
-
PBAC(Policy Based Access Control)
ユーザーにポリシーが付与されているかで、認可を行う方法です。別名、Attribute Based Access Controlともいいます。
-
RBAC(Role Based Access Control)
権限(管理者、ユーザー、ゲストなど)によって、認可を行うシンプルな方法です。
- ユーザーの加入プランなどに応じて、認可の条件を変えるなどを行いたい場合に、複雑性が増してしまう
- 同じ管理者でも支払い情報を変更できるのは、一部の管理者だけにするのは難しい
など、PBACと比べ、あまり柔軟性がないのが欠点です。
詳しくは下記を参考にしてください
設計
ここまでで、PBACの方が柔軟性が高いのが何となくわかっていただけたと思います。
小規模なプロダクトやMVP開発であれば、RBACでいいと思いますが、実際にプロダクトリリース後、ユーザーから前述のような要望が出てきた場合にも、対応していけるような設計をしたいと思います。
本稿の目標は、そこそこスケールしやすい認可処理です。
- とりあえず、 MVP開発としてRBACができれば十分だけど、今後、PBACにスケールしやすくしたい
- フロントエンドでPBACだから、PBACを採用したい(FE編でPBACを採用しているため)
ここからは、どのようにして、そこそこスケールしやすい認可処理を実装するのかを紹介していこうと思います。
認可の設計
採用するのは、PBACチックなRBACです。
PBACチックなRBACとは、ロール自体が複数のポリシーの集合(ポリシーグループ)にすることです。
これにより、アクセスがあった場合、ユーザーが該当のポリシーを持っているか否やのみを確認するだけで済むようになり、シンプルな認可が実現可能となります。
DB設計
今回の想定では、
- 権限によって使える機能を変更する
- 加入プランによって使える機能を変える
プラスαの要素として
- 個々のユーザーごとにポリシーをカスタマイズできるようにする
としています。
シンプルな設計
一旦、加入プランを外したシンプルな例をお話しします。
- User: サービスに登録しているユーザーです。
- Role: 権限のマスターです。
- Policy: ポリシーのマスターです。
- RolePolicy: 権限によるポリシーを取得するための中間テーブルです。
ユーザーの権限によって、保持しているポリシー一覧をRolePolicyから取得することが可能となり、RBACですが、PBACを実現可能となります。
アクセスがあった場合は、保持しているポリシー一覧に、アクセスしてきたコントローラーとアクションのポリシーがあるかを確認するだけで済みます。
加入プランの例
- Plan:加入プランのマスターです。
加入プランのマスターを追加することで、加入プランと権限を複合主キーとして、保有しているポリシーを取得します。
これにより、加入プランと権限ごとに機能制限をかけることができます。
他に要件が追加されたとしても、マスターを追加して、RolePolicyに複合主キーで検索しに行けばいいので、拡張性が高くなります。
スケールする場合
- UserPolicy: Userの保有するポリシーが入ったテーブル
UserPolicyを追加することで、ユーザーごとにカスタマイズしたポリシーを設定可能となります。
RolePolicyはポリシーのテンプレートして使用します。
ユーザー登録時には、RolePolicyからポリシーをコピーし、外したいポリシーのみUserPolicyから削除したり、逆に、RolePolicyにあるポリシーリストからポリシーを選択して、UserPolicyに設定するのも大丈夫です。
また、RolePolicyを用いて、UserPolicyにバリデーションを貼るようにしましょう。
加入しているプラン以外の機能や、一般ユーザーに管理者用のポリシーを誤って付与することを防ぐことができます。
UserPolicyの追加で、RBACから、 PBACへ移行でき、FEでの認可処理に変更を加えることなく、 BEのみの変更で済みます。
DBの速度的な話をすると、UserPolicyのレコードが肥大化し、検索が後々遅くなることは容易に想像できます。UserPolicyにJSON型を採用して、UserとUserPolicyを1対1の関係にするなど、各々のプロジェクトでカスタマイズすることをお勧めします。
実装
実装と言っても、今回はコードも細かく解説すると長くなってしまうので、概念のみの説明として終わりにさせてください。
ポリシーマスターの作成
Policyへ登録するポリシーは、Rakeタスクを使用して、ApplicationController::Base
を継承しているコントローラーを再起的に探索して、コントローラー名とアクション名を取得して、CSVに落としています。
RolePolicyに関しては、各権限、各料金プランのCSVを作成し、そこにポリシーを記述して、Seedでデータが入るようになっています。
簡単な認可
アクセスがあった場合、コントローラー名とアクション名を取得し、ユーザーがポリシーを保有しているかを確認する認可です。
私自身は、コントローラーの継承元にbefore_actionで書くことで、すべてのコントローラーに対して、自動的に認可を実行して、最低限の認可を行っています。
開発者のミスで、PolicyやRolePolicyに記載を忘れても、自動的に弾いてくれるので安全です。
複雑な認可
- 管理者であればすべてのデータを編集可能
- ユーザーだった場合は、自分のデータのみを編集可能
このような複雑な認可に関しては、Punditを採用し、Scope機能で実装しています。
終わり
最後まで読んでいただきありがとうございました。
概念や考察ばかりで具体性がなく、申し訳ありません。
次回、実際のコードを用いて、詳細な実装を紹介していこうかと思いますが、先に提案で終わってしまったFEの第3部を書こうか迷ってます。
PS
今年は雪が少ないく、スノーボードがあまり楽しめないと思っていましたが、今週は大寒波で結構降ってくれているので週末、スノーボードに行くのが楽しみですね。
Discussion