🛡️

バックエンドの認可について

2024/01/17に公開

どうもukmashiです。
年末にフロントエンドの認可について記事を書いたら、そこそこ読んでいただけたようで大変嬉しく思います。

FEに引き続き、BEでの認可について書いていこうと思います。
FEについて読んでいな方は読んでいただけると嬉しいです

https://zenn.dev/ukkyon/articles/e54b617406c643
https://zenn.dev/ukkyon/articles/9bac5194f91e53

概要

本稿は、バックエンドに関する認可に関する概念記事です。
DBレベルで解説するので、他の言語でも転用可能ですが、FW的に対応可能かはわかりません。

本稿では、認可を行う目的などお話しした後、実際のDB設計から、認可の実装概念を紹介していこうと思います。

簡単なSaaS製品で使えるくらいのそこそこスケールしやすい認可処理をを目標としています。
AWSのIAMくらい汎用性の高い認可は想定していません。

そこそこスケールしやすい認可処理の定義として

  • とりあえず、 MVP開発としてRBACができれば十分だけど、今後、PBACにスケールしやすくしたい
  • フロントエンドでPBACだから、PBACを採用したい(FE編でPBACを採用しているため)

とします。

なお、筆者の専門はRailsであるため、後半の実装の話では、Railsベースで話を進めさせていただきます。

認可について

まずは、認可を行う目的と、BEに認可を入れると何が嬉しいかを整理します。

認可を行う目的

FE編でも書いていますが、再掲します。
認可をすることで、適切なユーザーが、適切な処理を行うことが目的になります。

  1. Who is making the request?
    誰がリクエストしているのか?
  2. What are they trying to do?
    何をしようとしているのか?
  3. What are they doing it to?
    何のために?

これを認可三大要素(Three main aspects of authorization)といい、認可をする上で、常に問い続ける内容です。

例えば、Postコントローラーへのアクションがあった場合

  1. Current Userが
  2. PostControllerのIndexにリクエストを
  3. Postの一覧を取得するために

参考:認可のアーキテクチャに関する考察(Authorization Academy IIを読んで)

もっと認可について、詳細を知りたい場合は、こちらがおすすめ
めっちゃわかりやすいです。
https://zenn.dev/she_techblog/articles/6eff1f28d107be

BEに認可を入れる目的

BEに認可を入れる目的は、セキュリティを担保することです

責務分離の観点から

  • FEは描画に責任を持つ
  • BEはデータ操作に責任を持つ

のが仕事です。

故に、ユーザーが誤ったデータ操作を行えないよう、行動に制限をかけることにBEは目的を持っています。

認可の種類

ここで、簡単に認可の種類について紹介します。

  • PBAC(Policy Based Access Control)

    ユーザーにポリシーが付与されているかで、認可を行う方法です。別名、Attribute Based Access Controlともいいます。

  • RBAC(Role Based Access Control)

    権限(管理者、ユーザー、ゲストなど)によって、認可を行うシンプルな方法です。

    • ユーザーの加入プランなどに応じて、認可の条件を変えるなどを行いたい場合に、複雑性が増してしまう
    • 同じ管理者でも支払い情報を変更できるのは、一部の管理者だけにするのは難しい

    など、PBACと比べ、あまり柔軟性がないのが欠点です。

詳しくは下記を参考にしてください

https://www.okta.com/jp/blog/2020/09/attribute-based-access-control-abac/

設計

ここまでで、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部を書こうか迷ってます。

https://x.com/ukmshi

PS
今年は雪が少ないく、スノーボードがあまり楽しめないと思っていましたが、今週は大寒波で結構降ってくれているので週末、スノーボードに行くのが楽しみですね。

Discussion