このリポジトリはPHP Webアプリフレームワークである、LaravelのLTSバージョンである5.5の公式英文ドキュメントを日本語へ翻訳しています。
This project is maintained by okinaka
Laravelは組み込み済みの認証サービスに加え、特定のリソースに対するユーザーアクションを認可する簡単な手法も提供しています。認証と同様に、Laravelの認可のアプローチはシンプルで、主に2つの認可アクションの方法があります。ゲートとポリシーです。
ゲートとポリシーは、ルートとコントローラのようなものであると考えてください。ゲートはシンプルな、クロージャベースのアプローチを認可に対してとっています。一方のコントローラに似ているポリシーとは、特定のモデルやリソースに対するロジックをまとめたものです。最初にゲートを説明し、次にポリシーを確認しましょう。
アプリケーション構築時にゲートだけを使用するか、それともポリシーだけを使用するかを決める必要はありません。ほとんどのアプリケーションでゲートとポリシーは混在して使われますが、それで正しいのです。管理者のダッシュボードのように、モデルやリソースとは関連しないアクションに対し、ゲートは主に適用されます。それに対し、ポリシーは特定のモデルやリソースに対するアクションを認可したい場合に、使用する必要があります。
ゲートは、特定のアクションを実行できる許可が、あるユーザーにあるかを決めるクロージャのことです。通常は、App\Providers\AuthServiceProviderの中で、Gateファサードを使用し、定義します。ゲートは常に最初の引数にユーザーインスタンスを受け取ります。関連するEloquentモデルのような、追加の引数をオプションとして受け取ることもできます。
/**
* 全認証/認可サービスの登録
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
コントローラのように、ゲートはClass@method形式のコールバック文字列を使い定義することも可能です。
/**
* 全認証/認可サービスの登録
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'PostPolicy@update');
}
resourceメソッドを使用すれば、一度に複数のゲートを定義できます。
Gate::resource('posts', 'PostPolicy');
これは次のゲート定義とまったく同じです。
Gate::define('posts.view', 'PostPolicy@view');
Gate::define('posts.create', 'PostPolicy@create');
Gate::define('posts.update', 'PostPolicy@update');
Gate::define('posts.delete', 'PostPolicy@delete');
デフォルトとして、view、create、update、deleteアビリティが定義されます。resourceメソッドに第3引数として配列を渡し、デフォルトのアビリティのオーバーライドや追加ができます。配列のキーでアビリティの名前、値でメソッド名を定義します。例として、以下のコードで新しいposts.imageとposts.photoのゲート定義を作成してみます。
Gate::resource('posts', 'PostPolicy', [
'image' => 'updateImage',
'photo' => 'updatePhoto',
]);
ゲートを使用しアクションを認可するには、allowsとdeniesメソッドを使ってください。両メソッドに現在認証中のユーザーを渡す必要はないことに注目しましょう。Laravelが自動的にゲートクロージャにユーザーを渡します。
if (Gate::allows('update-post', $post)) {
// 現在のユーザーはこのポストを更新できる
}
if (Gate::denies('update-post', $post)) {
// 現在のユーザーはこのポストを更新できない
}
特定のユーザーがあるアクションを実行できる認可を持っているかを確認するには、GateファサードのforUserメソッドを使用します。
if (Gate::forUser($user)->allows('update-post', $post)) {
// 渡されたユーザーはこのポストを更新できる
}
if (Gate::forUser($user)->denies('update-post', $post)) {
// ユーザーはこのポストを更新できない
}
ポリシーは特定のモデルやリソースに関する認可ロジックを系統立てるクラスです。たとえば、ブログアプリケーションの場合、Postモデルとそれに対応する、ポストを作成/更新するなどのユーザーアクションを認可するPostPolicyを持つことになるでしょう。
make:policy Artisanコマンドを使用し、ポリシーを生成できます。生成したポリシーはapp/Policiesディレクトリに設置されます。このディレクトリがアプリケーションに存在していなくても、Laravelにより作成されます。
php artisan make:policy PostPolicy
make:policyコマンドは空のポリシークラスを生成します。基本的な「CRUD」ポリシーメソッドを生成するクラスへ含めたい場合は、make:policyコマンド実行時に--modelを指定してください。
php artisan make:policy PostPolicy --model=Post
{tip} 全ポリシーはLaravelの サービスコンテナにより依存解決されるため、ポリシーのコンストラクタに必要な依存をタイプヒントすれば、自動的に注入されます。
ポリシーができたら、登録する必要があります。インストールしたLaravelアプリケーションに含まれている、AuthServiceProviderにはEloquentモデルと対応するポリシーをマップするためのpoliciesプロパティを含んでいます。ポリシーの登録とは、指定したモデルに対するアクションの認可時に、どのポリシーを利用するかをLaravelへ指定することです。
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* アプリケーションにマップ付されたポリシー
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* アプリケーションの全認証/認可サービスの登録
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
ポリシーが登録できたら、認可するアクションごとにメソッドを追加します。たとえば、指定したUserが指定Postインスタンスの更新をできるか決める、updataメソッドをPostPolicyに定義してみましょう。
updateメソッドはUserとPostインスタンスを引数で受け取り、ユーザーが指定Postの更新を行う認可を持っているかを示す、trueかfalseを返します。ですから、この例の場合、ユーザーのidとポストのuser_idが一致するかを確認しましょう。
<?php
namespace App\Policies;
use App\User;
use App\Post;
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;
}
}
必要に応じ、様々なアクションを認可するために、追加のメソッドをポリシーに定義してください。たとえば、色々なPostアクションを認可するために、viewやdeleteメソッドを追加できます。ただし、ポリシーのメソッドには好きな名前を自由につけられることを覚えておいてください。
{tip} ポリシーを
--modelオプションを付け、Artisanコマンドにより生成した場合、view、create、update、deleteアクションが含まれています。
ポリシーメソッドの中には、現在の認証ユーザーのみを受け取り、認可するためのモデルを必要としないものもあります。この状況は、createアクションを認可する場合に、よく現れます。たとえば、ブログを作成する場合、どんなポストかにはかかわらず、そのユーザーが作成可能かを認可したいでしょう。
createのように、モデルインスタンスを受け取らないポリシーメソッドを定義する場合は、モデルインスタンスを受け取る必要はありません。代わりに、その認証済みユーザーが期待している人物かをメソッドで定義してください。
/**
* 指定されたユーザーがポストを作成できるかを決める
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
特定のユーザーには指定したポリシーの全アクションを許可したい場合があります。そのためには、beforeメソッドをポリシーへ定義してください。beforeメソッドはポリシーの他のメソッドの前に実行されるため、意図するポリシーメソッドが実際に呼び出される前に、アクションを許可する機会を提供します。この機能は主に、アプリケーションの管理者に全てのアクションを実行する権限を与えるために使用されます。
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
ユーザーに対して全認可を禁止したい場合は、beforeメソッドからfalseを返します。nullを返した場合、その認可の可否はポリシーメソッドにより決まります。
{note} クラスがチェックするアビリティと一致する名前のメソッドを含んでいない場合、ポリシークラスの
beforeメソッドは呼び出されません。
Laravelアプリケーションに含まれるUserモデルは、アクションを認可するための便利な2つのメソッドを持っています。canとcantです。canメソッドは認可したいアクションと関連するモデルを引数に取ります。例として、ユーザーが指定したPostを更新を認可するかを決めてみましょう。
if ($user->can('update', $post)) {
//
}
指定するモデルのポリシーが登録済みであれば適切なポリシーのcanメソッドが自動的に呼びだされ、論理型の結果が返されます。そのモデルに対するポリシーが登録されていない場合、canメソッドは指定したアクション名に合致する、ゲートベースのクロージャを呼びだそうとします。
createのようなアクションは、モデルインスタンスを必要としないことを思い出してください。そうした場合は、canメソッドにはクラス名を渡してください。クラス名はアクションを認可するときにどのポリシーを使用すべきかを決めるために使われます。
use App\Post;
if ($user->can('create', Post::class)) {
// 関連するポリシーの"create"メソッドが実行される
}
送信されたリクエストがルートやコントローラへ到達する前に、アクションを認可できるミドルウェアをLaravelは持っています。デフォルトでApp\Http\Kernelクラスの中でcanキーにIlluminate\Auth\Middleware\Authorizeミドルウェアが割り付けられています。あるユーザーがブログポストを認可するために、canミドルウェアを使う例をご覧ください。
use App\Post;
Route::put('/post/{post}', function (Post $post) {
// 現在のユーザーはこのポストを更新できる
})->middleware('can:update,post');
この例では、canミドルウェアへ2つの引数を渡しています。最初の引数は認可したいアクションの名前です。2つ目はポリシーメソッドに渡したいルートパラメータです。この場合、暗黙のモデル結合を使用しているため、Postモデルがポリシーメソッドへ渡されます。ユーザーに指定したアクションを実行する認可がない場合、ミドルウェアは403ステータスコードのHTTPレスポンスを生成します。
この場合も、createのようなアクションではモデルインスタンスを必要としません。このようなケースでは、ミドルウェアへクラス名を渡してください。クラス名はアクションを認可するときに、どのポリシーを使用するかの判断に使われます。
Route::post('/post', function () {
// 現在のユーザーはポストを更新できる
})->middleware('can:create,App\Post');
Userモデルが提供している便利なメソッドに付け加え、App\Http\Controllers\Controllerベースクラスを拡張しているコントローラに対し、Laravelはauthorizeメソッドを提供しています。canメソッドと同様に、このメソッドは認可対象のアクション名と関連するモデルを引数に取ります。アクションが認可されない場合、authorizeメソッドはIlluminate\Auth\Access\AuthorizationException例外を投げ、これはデフォルトでLaravelの例外ハンドラにより、403ステータスコードのHTTPレスポンスへ変換されます。
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 指定したポストの更新
*
* @param Request $request
* @param Post $post
* @return Response
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// 現在のユーザーはブログポストの更新が可能
}
}
既に説明してきたように、createのように、モデルインスタンスを必要としないアクションがあります。この場合、クラス名をauthorizeメソッドへ渡してください。クラス名はアクションの認可時に、どのポリシーを使用するのかを決めるために使われます。
/**
* 新しいブログポストの生成
*
* @param Request $request
* @return Response
*/
public function create(Request $request)
{
$this->authorize('create', Post::class);
// 現在のユーザーはブログポストを生成できる
}
Bladeテンプレートを書くとき、指定したアクションを実行できる認可があるユーザーの場合のみ、ページの一部分を表示したい場合があります。たとえば、実際にポストを更新できるユーザーの場合のみ、ブログポストの更新フォームを表示したい場合です。この場合、@canと@cannot系ディレクティブを使います。
@can('update', $post)
<!-- 現在のユーザーはポストを更新できる -->
@elsecan('create', App\Post::class)
<!-- 現在のユーザーはポストを作成できる -->
@endcan
@cannot('update', $post)
<!-- 現在のユーザーはポストを更新できない -->
@elsecannot('create', App\Post::class)
<!-- 現在のユーザーはポストを作成できない -->
@endcannot
これらのディレクティブは@ifや@unless文を使う記述に対する、便利な短縮形です。上記の@canと@cannot文に対応するコードは以下のようになります。
@if (Auth::user()->can('update', $post))
<!-- 現在のユーザーはポストを更新できる -->
@endif
@unless (Auth::user()->can('update', $post))
<!-- 現在のユーザーはポストを更新できない -->
@endunless
他の認可メソッドと同様に、アクションがモデルインスタンスを必要としない場合、@canと@cannotディレクティブへ、クラス名を渡すことができます。
@can('create', App\Post::class)
<!-- 現在のユーザーはポストを更新できる -->
@endcan
@cannot('create', App\Post::class)
<!-- 現在のユーザーはポストを更新できない -->
@endcannot