Laravelの認可処理

最近、Laravelの認可処理を実装する際に学んだことを書きます。

やりたいこと

  • ユーザーの役割に応じて、アクセス制御できるようにしたい。例えば、Adminユーザーだったら編集/削除/登録の操作できるけど、それ以外のユーザーの場合は、参照する権限しかない、みたいなことをできるようにしたい。

  • 認可処理で設けた利用制限について、どの処理に対して、どういう認可の処理が適用されてるかを php artisan route:list の実行結果で、すぐわかるようにしたい(なのでミドルウェアによる認可が必要。コントローラーの各アクションに定義すると、どの処理にどの認可処理が適用されてるか、route:list とかでぱっと見わからない)

  • ただ、Laravel 5.1を使っていて、ミドルウェアで認可できるようにしたいんだけ、5.1では対応してないようだったので、can ミドルウェアを laravel 5.1で使えるようにしたい

やったこと

  • Laravelで用意されてる認可処理、ポリシーを使った
  • Laravel 5.1でcanミドルウェアを使えるようする。Laravel 5.3以降で、Laravelが標準で用意しているcanミドルウェアのファイル(Illuminate/Auth/Middleware/Authorize.php)を5.1環境に配置して使えるようにする
  • ルートパラメーターからミドルウェアインスタンスを渡せるようにした(モデル結合ルート)。それを元にポリシーの認可の条件を書けるようにした

Laravelで用意されてる認可処理、ポリシーを使った

Laravelでは、認可処理が2種類用意されてて、詳細はドキュメント。 https://readouble.com/laravel/5.7/ja/authorization.html

  • 1つの認可処理に名前をつけて利用の可否を決定づけるゲート(Gate)

  • 複数の認可処理を記述できるポリシー(Policy)

これ、どういう状況でゲート使って、どういう状況でポリシー使った方が良いか、ちょっとよくわからなかったんだけど、使い分けとして、特定のリソースの操作に対して、認可する場合はポリシーを使って、グローバルで認可の制限をつけたい場合は、ゲートを使うのが良さそう。今回の場合でいうと、特定のリソースに対しての編集/削除/登録の認可になるので、ポリシーを使って書いた。Gateを使って良くなそうな例として、Gateで認可の条件を書いていくとすると、AuthServiceProviderのbootメソッドに、大量に処理を書いていく可能性があり、パッとみて、どういう制御がかかっているかわかりずらくないっていく懸念がありそう。特定のリソースに対する制御の場合は、ポリシーを使って書いていった方がわかりやすくて良さそう。以下、ポリシーを使う場合のざっくりした流れ。詳しいところはドキュメントに載ってる。

(1) xxxリソースに対応したポリシーを作成する

$ php artisan make:policy xxxxPolicy

(2) ポリシーを登録する。AuthServiceProvider の$policies に作成したポリシークラスと対応するEloquentモデルを記述する

(3) 作成したポリシーに認可する条件を記述する

(4) 作成したポリシーを適用したいアクションやミドルウェアに追加する

これで、ポリシーに記述した条件を満たしていないと、アクセスできない/できる みたいなことができる。この後、ブラウザからアクセスしてみて、403とかになれば良さそう。あと、ポリシーとは関係ないけど、ゲートを定義する際の名前の付け方について、

Gateは"誰が"、"何を", "どう", "できる/出来ない" を扱うもので、"誰が"はログインユーザーが第一引数から渡されることで自明なので "何を", "どう" が大切。なので名前の規則としてV-O (SVO) の形にしましょう。

っていうのがあって、名前のつけ方とかも重要なので気をつける。例えば、Adminユーザーだったら投稿できる。みたいなことを考えてみると、(canのFacadeを利用して書いてみることを考えると)、Admin can update Post になるのでupdate-post って感じで、Gate::defineに定義する。

Laravel 5.1でcanミドルウェアを使えるようにする

やったことは、laravel 5.7の Illuminate/Auth/Middleware/Authorize.php を、そのまま5.1のlaravelで独自ミドルウェアとして定義した。

(1)5.7環境で、Kernel.phpの$routeMiddlewareで定義されてる canミドルウェアで定義されてるAuthorizeクラスを確認

(2)それを、Authorizeを、5.1環境で独自ミドルウェアとして定義する

これで、routingにcanミドルウェアを使えるようになった。

ちょっと話が逸れるかもだけど、この作業してるときちょっと悩んだことについて書くと、Authorizeクラスを持ってくるとき、最初5.3から持ってきていて、それだとうまくいかなかった。それで色々やってて、5.7から持ってくるとうまくいった。これは、5.3から5.7の間でコードが変わってたからなんだけど、どう変わってたかというと、

5.6まではcan middlewareでユーザーがログインしているかをチェックしていた。 $this->auth->authenticate() . 5.6? 5.7? でcan middlewareはguestも受け付けるようになったのでログインしているかどうかはチェックしなくなったらしい。なのでcanでログインしてるかしていないかはチェックしないと思って使えば良いので5.7のコードで問題なさそう

めちゃめちゃ勉強になった。あと、githubの使い方で、ブランチ間の差分を見る方法もを知らなかったので、勉強になった。

Authorize.php 5.3と5.7の差分

github.com

Authorize.phphistory https://github.com/laravel/framework/commits/5.7/src/Illuminate/Auth/Middleware/Authorize.php

ルートパラメーターからミドルウェアインスタンスを渡せるようにした(モデル結合ルート)

ここまでやって、canミドルウェアを使えるようになったし、ポリシーも登録できたんだけど、特定のリソースにアクセスする際の認可をポリシーで、うまく書けていなかった。(例えば、自分が投稿したリソースだったら、アクセスできる。他の人はNG。みたいな認可の処理を書きたい)もう少しいうと、ルートパラメーターのidを元にしたリソースのインスタンスを、ポリシーで定義してる認可の条件で使えるようにしたいけど、どうやってやればいいかわからなかった。結果的には、ドキュメントのLaravel 5.1 HTTPルーティングのモデル結合ルートに書いてあることを定義すると、うまく渡せるようになった。(RouteServiceProvider::bootにモデル結合を定義する)

ドキュメント https://readouble.com/laravel/5.1/ja/routing.html#implicit-binding

これでうまくいったんだけど、ここまでくるのにいろいろと悩んでて、その辺りを書くと、canミドルウェアを使って、routingにポリシーを定義する方法がわからず、めちゃめちゃ悩んだ。今振り返ると、ドキュメントにちゃんと書いてあるんだけど、正しく読めていなかった。その際にやったこととしては、Authorize.phpの処理をxdebugでとめまくって、コードの深いところまで、追いまくって、どこの条件でおかしくなってるのか調べたりした。結果的には、やはりドキュメントに書いてある通りに定義されてなかった(引数の指定の仕方がおかしいなど)のが原因だったことがわかったんだけど、定義元ジャンプしまくって、コードを深く追いまくったおかげで、実際どういう処理が書かれてるのかを知れたので、すごく勉強になったと思う。(追ってる間のコード、ちゃんと読めてないのもあるかと思うので、もう少し読み解けるようなりたい。最後のあたりで make ってやつが出てきたりしたけど、よくわかってなくて、その辺り気になってる)

f:id:shimabukuromeg:20181202143121p:plain

まとめ、次やること

  • ポリシーとゲートに違いとか、Laravelで認可の処理書くの、基本的なとこだけど少し詳しくなれたと思う。
  • コードを追っていくやつ、もう少しやってみる。個人的に、ファサードとかDIとかサービスコンテナとかいまいちよくわかってなくて、コード追うやつやりながら、ドキュメントとか読んだりすると理解できそうな気がしてる。あんまりよくわかってないけど。