イントロダクション
初めから用意されている認証サービスに付け加え、Laravelは認可ロジックを取りまとめ、リソースへのアクセスをコントロールする簡単な手段を提供します。認可ロジックを組織立てるのに役立つメソッドやヘルパはたくさん用意されていますので、このドキュメントで紹介します。
注意: 認可機能はLaravel 5.1.11で追加されました。この機能をアプリケーションに統合する前に、アップグレードガイドを参照してください。
アビリティの定義
あるユーザーが指定されたアクションを実行しても良いかをシンプルに判定するには、Illuminate\Auth\Access\Gate
クラスを使い「アビリティ(ability)」を定義してください。Laravelに初めから用意されているAuthServiceProvider
は、アプリケーションの全アビリティを定義するために便利な場所です。例として現在のUser
とPost
モデルを受け取る、update-post
アビリティを定義してみましょう。このアビリティの中で、ユーザーのid
がポストのuser_id
と一致するかを判定します。
<?php
namespace App\Providers;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* 全認証/認可サービスの登録
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
}
}
渡ってきた$user
がNULL
ではないかの、チェックを行っていないことに注目です。Gate
はユーザーが認証されていないか、forUser
メソッドを使いユーザーが指定されていない場合は、全アビリティから自動的にfalse
を返します。
クラスベースのアビリティ
認可のコールバックとして「クロージャー」を登録する方法に加え、クラス名とメソッドの文字列を引数に渡してもクラスメソッドを登録できます。必要であれば、クラスはサービスコンテナを利用し、依存解決されます。
$gate->define('update-post', 'Class@method');
認可チェックの停止
場合により、特定のユーザーに対しては全アビリティーを許可したい場合があります。この場合は他の全認可チェックの前に実行されるコールバックを定義するbefore
メソッドを使ってください。
$gate->before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
before
コールバックがnullではない結果を返した場合、それをチェック結果として取り扱います。
after
メソッドを使い、全部の認可のチェックを行った後に実行するコールバックを定義できます。しかしafter
のコールバックの中から認可チェックの結果を変更できません。
$gate->after(function ($user, $ability, $result, $arguments) {
//
});
アビリティーの確認
Gateファサードによる確認
アビリティーを定義したら、さまざまな方法で「確認」できます。最初はGate
ファサードのcheck
、allow
、denies
メソッドを使う方法です。これらのメソッドはアビリティ名とコールバックに渡した引数を受け取ります。Gate
が自動的に現在のユーザーをコールバック渡す引数の先頭に付け加えるため、こうしたメソッドに現在のユーザーを渡す必要はありません。ですから既に定義したupdate-post
アビリティーを確認する場合、denies
メソッドにはPost
インスタンスを渡す必要があるだけです。
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 指定したポストの更新
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update-post', $post)) {
abort(403);
}
// ポストの更新処理…
}
}
当然のことながら、allows
メソッドはdenies
メソッドをただひっくり返した働きをし、アクションが認可されていればtrue
を返します。check
メソッドはallows
メソッドのエイリアスです。
特定のユーザーに対するアビリティーを確認
現在認証されているユーザーではなく、別のユーザーが指定したアビリティーを持っているかをGate
ファサードで確認したい場合は、forUser
メソッドを使います。
if (Gate::forUser($user)->allows('update-post', $post)) {
//
}
複数の引数の指定
もちろんアビリティのコールバックには複数の引数が指定できます。
Gate::define('delete-comment', function ($user, $post, $comment) {
//
});
アビリティーで複数の引数が必要であれば、Gate
ファサードのメソッドに引数を配列として渡してください。
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
Userモデルによる確認
他のやり方として、アビリティーをUser
モデルインスタンスでも確認できます。LaravelのApp\User
モデルは、can
とcannot
メソッドを提供しているAuthorizable
トレイトをuseしています。これらのメソッドは、Gate
ファサードのallow
とdenies
メソッドと使い方が似ています。では、前の例と同様に、コードを次のように変更してみましょう。
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 指定したポストの更新
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return Response
*/
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
if ($request->user()->cannot('update-post', $post)) {
abort(403);
}
// ポストの更新処理…
}
}
もちろん、can
メソッドは単にcannot
メソッドの真反対の働きです。
if ($request->user()->can('update-post', $post)) {
// Update Post...
}
Bladeテンプレートでの確認
現在の認証ユーザーが指定したアビリティーを持っているかを簡単に確認するのに便利なように、Laravelは@can
Bladeディレクティブを用意しています。例を見てください。
<a href="/post/{{ $post->id }}">ポスト表示</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">ポスト編集</a>
@endcan
@can
ディレクティブは、@else
ディレクティブと組み合わせても使えます。
@can('update-post', $post)
<!-- 現在のユーザーはポストを更新できる -->
@else
<!-- 現在のユーザーはポストを更新できない -->
@endcan
フォームリクエストでの確認
Gate
で定義したアビリティーをフォームリクエストのauthorize
メソッドで活用する選択を取ることもできます。
/**
* ユーザーがこのリクエストを作成できる認可があるかの判定
*
* @return bool
*/
public function authorize()
{
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
ポリシー
ポリシーの作成
全認可ロジックをAuthServiceProvider
の中で定義するのは、大きなアプリケーションでは厄介ですから、Laravelでは認可ロジックを「ポリシー」クラスに分割できます。ポリシーは普通のPHPクラスで、認可するリソースに基づいてロジックをグループ分けするものです。
始めにPost
モデルの認可を管理するポリシーを生成しましょう。make:policy
Artisanコマンドでポリシーを生成します。生成したポリシーはapp/Policies
ディレクトリーへ設置されます。
php artisan make:policy PostPolicy
ポリシーの登録
ポリシーができたら、Gate
クラスで登録する必要があります。AuthServiceProvider
には様々なエンティティを管理するポリシーとマップするためのpolicies
プロパティを含んでいます。では、Post
モデルのポリシーであるPostPolicy
クラスを指定しましょう。
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* アプリケーションにマップするポリシー
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* アプリケーションの全認証/認可サービスを登録
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
ポリシーの記述
ポリシーを生成し登録したら、認可する各アビリティーのためのメソッドを追加できます。指定したUser
がPost
を「更新(update)」できるかを判定する、update
メソッドをPostPolicy
に例として定義してみます。
<?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;
}
}
認可する様々なアビリティーを必要なだけポリシーにメソッドとして追加定義してください。たとえばshow
やdestroy
、addComment
メソッドなど、多様なPost
アクションの認可が定義できるでしょう。
注目: 全ポリシーはLaravelのサービスコンテナにより依存解決されます。つまりポリシーのコンストラクターに必要な依存をタイプヒントで指定すれば、自動的に挿入されます。
全チェックの停止
場合により特定のユーザーにポリシーの全アビリティーを許可したい場合があります。この状況ではポリシーにbefore
メソッドを定義してください。このメソッドはポリシーにある他の全認可チェックより先に実行されます。
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
before
メソッドがnullでない値を返した場合、それをチェックの結果として取り扱います。
ポリシーの確認
ポリシーメソッドはクロージャーベースの認可コールバックと全く同じ方法で呼び出します。Gate
ファサードやUser
モデル、@can
Bladeディレクティブ、policy
ヘルパを使用できます。
Gateファサードによる確認
Gate
はメソッドに渡された引数のクラスを調べ、どのポリシーを使用するか自動的に決定します。ですからPost
インスタンスをdenies
メソッドに渡せば、Gate
は認可するアクションに合ったPostPolicy
を使用します。
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 指定ポストの更新
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update', $post)) {
abort(403);
}
// ポストの更新処理…
}
}
Userモデルによる確認
User
モデルのcan
とcannot
メソッドも引数に指定された引数に対する使用可能なポリシーがあれば、自動的に使用します。これらのメソッドはアプリケーションが取得したUser
インスタンス全てに対するアクションを認可するために、便利な手法を提供しています。
if ($user->can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
Bladeテンプレートによる確認
同様に、@can
Bladeディレクティブも与えられた引数に対して使用可能なポリシーを活用します。
@can('update', $post)
<!-- 現在のユーザーはポストを更新できる -->
@endcan
policyヘルパによる確認
グローバルなpolicy
ヘルパ関数は、指定されたクラスインスタンスに対する「ポリシー」クラスを取得するために使用します。例えば、Post
インスタンスをpolicy
ヘルパに渡し、対応するPostPolicy
クラスのインスタンスを取得できます。
if (policy($post)->update($user, $post)) {
//
}
コントローラーの認可
Laravelに含まれるデフォルトのApp\Http\Controllers\Controller
基本クラスでは、AuthorizesRequests
トレイトがuseされています。このトレイトはauthorize
メソッドを提供しており、簡単に指定アクションを認可するために使用でき、アクションが認可されていなければHttpException
を投げます。
authorize
メソッドはGate::allows
や$user->can()
などの認可メソッドと同じ使用方法です。では、Post
を更新するリクエストを手っ取り早く認可するために、authorize
メソッドを用いてみましょう。
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 指定ポストの更新
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize('update', $post);
// ポストの更新処理…
}
}
アクションが認可されていれば、コントローラーは通常通り続けて実行されます。しかしauthorize
メソッドがアクションを非認可と判断すると、HttpException
が自動的に投げられ、
403 Not Authorized
ステータスコードのHTTPレスポンスが生成されます。ご覧の通りにauthorize
メソッドはコード1行でアクションを認可し、例外を投げるための便利で手っ取り早い方法です。
AuthorizesRequests
トレイトは、現在認証中ではない別のユーザーのアクションを認可する、authorizeForUser
メソッドも提供しています。
$this->authorizeForUser($user, 'update', $post);
ポリシーメソッドの自動決定
ポリシーのメソッドはコントローラーのメソッドと頻繁に対応します。たとえば前記のupdate
メソッドのように、update
と言う名前がコントローラーメソッドとポリシーメソッドで共通です。
そのため、Laravelはauthorize
メソッドの引数にインスタンスだけを渡すことも許しています。認可するアビリティは呼び出しているメソッドの名前を元に自動的に決められます。この例では、コントローラーのupdate
メソッドからauthorize
が呼びだされていますから、PostPolicy
上でもupdate
メソッドが呼びだされます。
/**
* 指定ポストの更新
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize($post);
// ポストの更新…
}