ブログ
 

【Laravel】認可 Policyの使い方

       

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

Policyとは、特定のモデルに対してのアクション(作成、閲覧、更新、削除など)に関してアクセス制限を行います。
この記事ではPolicyについてまとめていますが、Gateと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=(紐づくモデル名)を指定します。viewAny、view、create、update、delete、restore、forceDeleteの7つのメソッドが記述された形で作成されます。他にも、自分で好きな名前のメソッドを定義することも可能です。

$ php artisan make:policy PostPolicy --model=Post
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の登録

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

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

policyの自動検出

AuthServiceProvider.phpに手動で紐付けをしましたが、以下に記述しているModelとPolicyの標準命名規則にしたがっているPolicyがある場合は、Laravelが自動で検出してくれます。(autodiscovery機能)

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

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

Policyの記述

Policyのメソッド

Policyが登録できたら、認可するアクションごとにメソッドを追加します。今回は、ログインしたUserが指定Postインスタンスの更新をできるか決める、updataメソッドをPostPolicyに定義しています。updateメソッドはUserとPostインスタンスを引数で受け取り、ユーザーが指定Postの更新を行う認可を持っているかを示すbool値、trueかfalseを返します。この例の場合、ログインしたUserのidと更新をしようとしているPostのuser_idが一致するかを確認しています。
※第二引数に紐づいているモデル(今回で言うとPost)のインスタンスを指定しないとうまくPolicyが動きません。

class PostPolicy
{
    /**
     * ログインユーザーが特定ポストの更新が可能であるか
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Post  $post // 第二引数は紐づいているモデルのインスタンスでないといけない
     * @return bool
     */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

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

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

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

public function update(Post $post){
    $this->authorize('update', $post);
    $post->update();

     return redirect('/posts');
}

authorizeの第一引数はPostPolicy.phpファイル内に記述したupdateメソッドに対応しています。第二引数には、Postモデルの変数$postを指定しています。PostPolicyのupdateメソッドの第一引数である「$user」は、Laravelがログインしているユーザーのインスタンスを自動で付与してくれています。

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"メソッドが実行される
        return redirect(route('post.show', []))->with('success', '登録に失敗しました。');
    } else {
                return redirect(route('post.show', []))->with('warning', '登録に成功しました。');
    }
}

canメソッドではユーザのidとブログを作成したユーザのidが一致しない場合は、403ステータスコードのHTTPレスポンスは生成されません。そのため、else内を記述して明示的にリダイレクトさせてあげる必要があります。

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

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