フレームワーク
 

【Laravel】Policy(ポリシー)の使い方

       

Laravelの認可はシンプルで、主にGate(ゲート)とPolicy(ポリシー)という2つの認可アクションの方法があります。

GateとPolicyは、ルートとコントローラのようなもので、Gateはシンプルなクロージャベースのアプローチを認可に対してとっています。一方のコントローラに似ているPolicyとは、特定のモデルに対してのアクション(作成、閲覧、更新、削除など)に関してアクセス制限を行います。Gateもモデルに対するアクションにアクセス制限を行うことは可能ですが、GateとPolicyどちらも利用しながらアクセス制限を行います。この記事ではPolicyについてまとめています。

Policyの作成

Policyの生成

先ほど説明した通り、Policyは特定のモデルに対してのアクションにアクセス制限が行えます。今回は、UserモデルとPostモデルをもつ、ブログアプリケーションを例に進めていきたいと思います。
はじめに、ブログ記事の作成や更新などのユーザーアクションを認可する「PostPlicy」を生成します。make:policy Artisanコマンドを実行すると、app/Policiesディレクトリが生成され、その中にPostPolicy.phpが生成されます。

$ php artisan make:policy PostPolicy

作成されるファイルの中身は下記の通りでメソッドは何も記述されていません。

namespace App\Policies;

use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{     use HandlesAuthorization;

    /**
     * Create a new policy instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }
}

基本的な「CRUD」ポリシーメソッドを生成するクラスへ含めたい場合は、make:policyコマンド実行時に–modelを指定します。

$ php artisan make:policy PostPolicy --model=Post

policyの登録

作成したPolicyの情報はAuthServiceProvider.phpに登録を行う必要があります。登録を行うことでPostモデルとPostPolicyが紐づけられます。

protected $policies = [
    'App\Post' => 'App\Policies\PostPolicy',
];

policyの自動検出

AuthServiceProvider.phpに手動で紐付けをしましたが、ModelとPolicyの標準命名規則にしたがっているPolicyを自動的に見つけてきてくれます。(autodiscovery機能)
具体的に説明すると、

①Modelがappディレクトリ下にあれば、Policy.phpはapp/Policiesディレクトリ内へ配置
②Policyの名前は対応するModelの名前+Policyサフィックスを付けたもの(Userモデルに対応させるには、UserPolicyクラス)

上記の①、②を守ることで、Laravelが自動で検出してくれます。
ここで注意したいことは、AuthServiceProvider.php中で紐付けされたPolicyは、自動検出される可能性のあるPolicyよりも優先的に扱われます。

Policyの記述

Policyのメソッド

Policyが登録できたら、認可するアクションごとにメソッドを追加します。今回は、指定したUserが指定Postインスタンスの更新をできるか決める、updataメソッドをPostPolicyに定義してみます。updateメソッドはUserとPostインスタンスを引数で受け取り、ユーザーが指定Postの更新を行う認可を持っているかを示す、trueかfalseを返します。この例の場合、UserのidとPostのuser_idが一致するかを確認しています。

class PostPolicy
{
    /**
     * ユーザーにより指定されたポストが更新可能か決める
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return bool
     */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

必要に応じ、さまざまなアクションを認可するために、メソッドをポリシーに追加できます。たとえば、viewやdeleteメソッドを追加できます。ここではviewやdeleteメソッド名以外にも、好きな名前を自由につけられます。Policyを-modelオプションを付け、Artisanコマンドで生成した場合、PostPolicy.phpファイルには、viewAny、view、create、update、delete、restore、forceDeleteの7つのメソッドが記述された形で作成されます。

namespace App\Policies;

use App\User;
use App\Post;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;
    
    /**
     * Determine whether the user can view any posts.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function viewAny(User $user)
    {
        //
    }

    /**
     * Determine whether the user can view the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function view(User $user, Post $post)
    {
        //
    }
   // 以下省略

Policyを使ったアクションの認可

authorizeメソッドを利用した制限

authorizeメソッドを記述することでポリシーでアクセス制限を行うことができます。更新に関する制限なので、コントローラーのupdateメソッドの中に記述しています。

public function update(Post $post){

    $this->authorize('update', $post);

    $post->update();

   return redirect('/posts');

}

authorizeの最初に引数がPostPolicy.phpファイル内に記述したupdateメソッドを対応します。2番目の引数には、Postモデルの変数$postを指定しています。PostPolicy.phpの中で設定したupdateメソッドの結果、アクセスしたユーザのidとブログを作成したユーザidが一致すればそのまま処理を行います。しかし、ユーザidとブログの作成者のidが一致しない場合は、ブラウザに「403 unauthorized」が表示されます。

canメソッドを利用した制限

authorizeメソッドではなくcanメソッドも利用することができます。canメソッドを利用する場合は、$userを使ってcanメソッドを使用するためauth()ヘルパー関数等を利用してユーザ情報を取得しておく必要があります。

public function update(Post $post){

    $user = auth()->user(); // アクセスしているユーザ情報を取得

    if($user->can('update',$post)){

    	// 関連するポリシーの"update"メソッドが実行される

    }
}

canメソッドではユーザのidとブログを作成したユーザのidが一致しない場合は、403ステータスコードのHTTPレスポンスは生成されません。

middleware(ミドルウェア)を利用した制限

middleware(ミドルウェア)を利用してアクセス制限を行うこともできます。下記ではweb.phpファイルのルーティングでmiddlewareの設定を行なっています。

use App\Post;

Route::put('/posts/{post}', 'PostController@update')->middleware('can:update,post');

canミドルウェアへ2つの引数を渡しています。最初の引数(update)は認可したいアクションの名前です。2つ目(post)はポリシーメソッドに渡したいルートパラメータです。この場合、暗黙のモデル結合を使用しているため、Postモデルがポリシーメソッドへ渡されます。ユーザーに指定したアクションを実行する認可がない場合、ミドルウェアは403ステータスコードのHTTPレスポンスを生成します。

最後に

-modelオプションを利用してポリシーを作成するとview、updateなどのメソッドが記述された形でPolicyファイルが作成されました。これらのメソッドは、コントローラーのメソッドと関連を持っています。

Controllerメソッド Policyメソッド
index viewAny
show view
create create
store create
edit update
update update
delete delete

コントローラーのshowメソッドは、ポリシーのviewメソッドと関連を持っているので、下記のようにコントロラーのshowメソッドの中でauthorizeメソッドを実行すると自動でPostPolicy.phpファイルのviewメソッドが実行されます。

public function show(Post $post){

    $this->authorize($post); // ※第一引数に'view'を入れなくても自動で呼び出せる

    return view('posts.show',compact('post'));

}

※showメソッドの中でauthorizeメソッドを実行する際に引数にviewを入れなくても自動でviewメソッドが呼ばれます。

また、コントローラーの__constructメソッドにauthorizeResourceメソッドを追加するとコントローラーの個別のメソッドでauthorizeメソッドを使わなくてもPolicyファイルで指定したメソッドを有効にすることができます。

class PostController extends Controller
{

    public function __construct(){
        $this->authorizeResource(Post::class);
    }

authorizeResourceメソッドを設定することでshowメソッドの中で個別にauthorizeメソッドを使っている場合は、その行を削除してもshowメソッドのアクセス制限を行うことができます。

Policyを使用すると効率よく認可設定が行えることが理解できたかと思います。
実際に使用することで、いろいろな発見ができると思うので、ぜひ使用してみてください。

 
  • このエントリーをはてなブックマークに追加