業務で新規アプリケーションの認可の設計・実装を行った。こういったものは新規開発じゃないとなかなか触れない部分で、忘れっちゃいそうなので、その際に勉強した認可・権限管理の基礎概念をまとめておく。
認可とは、「ユーザが何をできるか」をコントロールするプロセス。似ている概念として認証
があるが、これは「ユーザが誰であるか」を確認するプロセスである。
認可のプロセスでは、「認可三大要素」をもとに認可の判断を下すことになる。「認可三大要素」は以下の3つである。
例えば、GitHubのようなソースコードの管理アプリケーションを想定すると、認可三大要素は以下のようになる。
認可には、制御のメカニズムを表現するモデルが何種類か存在する。
引用: https://www.osohq.com/academy/relationship-based-access-control-rebac
RBAC(アールバックと発音するらしい)は、人やグループに「ロール」を割り当て、そのロールに基づいて権限を制御するモデルである。例えば、「Admin」ロールを持つユーザのみ、重要なAPIにアクセスできる、などである。
ReBACは、リソース間の「関係」に基づいて権限を制御するモデルである。例えば、フォルダへのアクセス権限を持つユーザは、フォルダ内のファイルへのアクセス権限を持つ、といったような制御ができる。RBACよりも柔軟な制御が可能になる。
ABACは、リソースの「属性」に基づいて権限を制御するモデルである。このモデルでは、実質的に無限の認可ロジックを適用することができる。例えば、isPublicがTrueのリソースには誰でもアクセスできる、業務時間外は機密ファイルにアクセスできない、といったような制御が可能になる。ReBACはABACの一種だと言うことができる。
認可は、ユーザからすると正しく実装されて「当たり前」でありながら、バグがあると致命的な問題になりえる領域である。しかも、アプリケーションへの組み込み方をミスると簡単にバグや実装漏れが起きてしまう。にも関わらず、認可をどのようにアプリケーションに組み込むか、といった知見は(自分の印象では)あまり共有されていないように感じる。
認可について意識しないままアプリケーションを実装していくと、以下のようにアプリケーションロジックと認可のロジックが混ざってしまいがちだと思う。
if user.isAdmin {
// アドミン向けの処理
} else {
// その他のユーザ向けの処理
}
// 上記のようなロジックがソースコードの色んな場所に分散して存在する
上記のように、認可とアプリケーションロジックが分離できていないと、実装ミスが発生しやすく、変更容易性も低くなります。
ではどのようにアプリケーションに組み込むかというと、認可のロジックを一箇所に集約して、それを各所で呼び出すようにする。認可の用語では、認可の判定を下すことをDecision
、その結果を適用することをEnforecement
と呼ぶ。以下の画像のように、Decision
は一箇所で集中管理してテストを厚く書き、アプリケーションの必要な箇所でEnforcement
を行う。重要なのは、認可のロジックが一箇所にまとまっていることで、認可の適用はどこでやろうが何回やろうがかまわない。
引用: https://www.osohq.com/academy/what-is-authorization#addingauthzapp
認可を適用(Enforcement
)する箇所にもパターンがある。
個人的には、リクエストの入り口箇所で適用するのが良いと考えている(し、そういうアプリケーションが多いのではないだろうか?)。理由は、この層で認可の判定ができていれば、それ以降のアプリケーションコードでは権限について考えなくてすむためである。
また、APIのパス・メソッドは初期に定義してから頻繁に変更することは少ないため、安定して使うことができる。Webアプリケーションフレームワークのミドルウェアで適用すれば、権限の適用漏れも防げる。
引用: https://www.osohq.com/academy/what-is-authorization#addingauthzapp
ほとんどのアプリケーションでは、認可のロジックはアプリケーション内に実装することが望ましい。一方、巨大なサービスやマイクロサービスアーキテクチャを採用している場合は、認可専用のマイクロサービスを追加して認可のロジックを集中管理する。このアーキテクチャを採用しているのが、GoogleのZanzibarである。
自分が開発したことがあるのは前者のみなので、ここらへんの詳細はこちらを参照してください。