除了提供“開(kāi)箱即用”的認(rèn)證
服務(wù)之外,Laravel
還提供了一個(gè)簡(jiǎn)單的方式來(lái)管理授權(quán)
邏輯以便控制對(duì)資源的訪問(wèn)權(quán)限。在 Laravel 中,有很多種方法和幫助函數(shù)來(lái)協(xié)助你管理授權(quán)邏輯,本文檔
將會(huì)一一覆蓋這些方法。
注意:授權(quán)在 Laravel 5.1.11 版本中引入,在將該特性集成到應(yīng)用之前請(qǐng)參考升級(jí)指南。
判斷用戶是否有權(quán)限執(zhí)行給定動(dòng)作的最簡(jiǎn)單方式就是使用 Illuminate\Auth\Access\Gate
類(lèi)來(lái)定義一個(gè)“權(quán)限”。我們?cè)?AuthServiceProvider 中定義所有權(quán)限,例如,我們來(lái)定義一個(gè)接收當(dāng)前 User
和 Post
模型的 update-post
權(quán)限,在該權(quán)限中,我們判斷用戶 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{
/**
* 注冊(cè)應(yīng)用所有的認(rèn)證/授權(quán)服務(wù).
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
parent::registerPolicies($gate);
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
}
}
注意我們并沒(méi)有檢查給定$user
是否為 NULL,當(dāng)用戶未經(jīng)過(guò)登錄認(rèn)證或者用戶沒(méi)有通過(guò) forUser
方法指定,Gate 會(huì)自動(dòng)為所有權(quán)限返回 false。
基于類(lèi)的權(quán)限 除了注冊(cè)授權(quán)回調(diào)閉包之外,還可以通過(guò)傳遞包含權(quán)限類(lèi)名和類(lèi)方法的方式來(lái)注冊(cè)權(quán)限方法,當(dāng)需要的時(shí)候,該類(lèi)會(huì)通過(guò)服務(wù)容器進(jìn)行解析:
$gate->define('update-post', 'PostPolicy@update');
權(quán)限定義好之后,可以使用多種方式來(lái)“檢查”。首先,可以使用 Gate
門(mén)面的 check
, allows
, 或者 denies
方法。所有這些方法都接收權(quán)限名和傳遞給該權(quán)限回調(diào)的參數(shù)作為參數(shù)。你不需要傳遞當(dāng)前用戶到這些方法,因?yàn)?Gate
會(huì)自動(dòng)附加當(dāng)前用戶到傳遞給回調(diào)的參數(shù),因此,當(dāng)檢查我們之前定義的 update-post
權(quán)限時(shí),我們只需要傳遞一個(gè) Post
實(shí)例到 denies
方法:
<?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);
}
// 更新文章...
}
}
當(dāng)然,allows
方法和 denies 方法是相對(duì)的,如果動(dòng)作被授權(quán)會(huì)返回 true ,check 方法是 allows 方法的別名。
為指定用戶檢查權(quán)限
如果你想要使用 Gate
門(mén)面判斷非當(dāng)前用戶是否有權(quán)限,可以使用 forUser
方法:
if (Gate::forUser($user)->allows('update-post', $post)) {
//
}
傳遞多個(gè)參數(shù) 當(dāng)然,權(quán)限回調(diào)還可以接收多個(gè)參數(shù):
Gate::define('delete-comment', function ($user, $post, $comment) {
//
});
如果權(quán)限需要多個(gè)參數(shù),簡(jiǎn)單傳遞參數(shù)數(shù)組到 Gate
方法:
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
還可以通過(guò) User
模型實(shí)例來(lái)檢查權(quán)限。默認(rèn)情況下,Laravel 的 App\User
模型使用一個(gè) Authorizabletrait 來(lái)提供兩種方法:can 和 cannot。這兩個(gè)方法的功能和 Gate
門(mén)面上的 allows
和 denies
方法類(lèi)似。因此,使用我們前面的例子,可以修改代碼如下:
<?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);
}
// 更新文章...
}
}
當(dāng)然,can
方法和 cannot
方法相反:
if ($request->user()->can('update-post', $post)) {
// 更新文章...
}
為了方便,Laravel 提供了 Blade 指令@can
來(lái)快速檢查當(dāng)前用戶是否有指定權(quán)限。例如:
<a href="/post/{{ $post->id }}">View Post</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan
你還可以將 @can
指令和@else
指令聯(lián)合起來(lái)使用:
@can('update-post', $post)
<!-- The Current User Can Update The Post -->
@else
<!-- The Current User Can't Update The Post -->
@endcan
你還可以選擇在表單請(qǐng)求
的 authorize
方法中使用 Gate
定義的權(quán)限。例如:
/**
* 判斷請(qǐng)求用戶是否經(jīng)過(guò)授權(quán)
*
* @return bool
*/
public function authorize(){
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
由于在 AuthServiceProvider
中定義所有的授權(quán)邏輯將會(huì)變得越來(lái)越臃腫笨重,尤其是在大型應(yīng)用中,所以 Laravel 允許你將授權(quán)邏輯分割到多個(gè)“策略”類(lèi)中,策略類(lèi)是原生的 PHP 類(lèi),基于授權(quán)資源對(duì)授權(quán)邏輯進(jìn)行分組。
首先,讓我們生成一個(gè)策略類(lèi)來(lái)管理對(duì) Post
模型的授權(quán),你可以使用 Artisan 命令 make:policy
來(lái)生成該策略類(lèi)。生成的策略類(lèi)位于 app/Policies
目錄:
php artisan make:policy PostPolicy
注冊(cè)策略類(lèi)
策略類(lèi)生成后我們需要將其注冊(cè)到 Gate
類(lèi)。AuthServiceProvider
包含了一個(gè) policies
屬性來(lái)映射實(shí)體及管理該實(shí)體的策略類(lèi)。因此,我們指定 Post
模型的策略類(lèi)是 PostPolicy
:
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider{
/**
* 應(yīng)用的策略映射
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
}
策略類(lèi)生成和注冊(cè)后,我們可以為授權(quán)的每個(gè)權(quán)限添加方法。例如,我們?cè)?PostPolicy
中定義一個(gè) update
方法,該方法判斷給定 User
是否可以更新某個(gè) Post
:
<?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;
}
}
你可以繼續(xù)在策略類(lèi)中為授權(quán)的權(quán)限定義更多需要的方法,例如,你可以定義 show
, destroy
, 或者 addComment
方法來(lái)認(rèn)證多個(gè) Post
動(dòng)作。
注意:所有策略類(lèi)都通過(guò)服務(wù)容器進(jìn)行解析,這意味著你可以在策略類(lèi)的構(gòu)造函數(shù)中類(lèi)型提示任何依賴,它們將會(huì)自動(dòng)被注入。
策略類(lèi)方法的調(diào)用方式和基于授權(quán)回調(diào)的閉包一樣,你可以使用 Gate
門(mén)面,User
模型,@can
指令或者幫助函數(shù) policy
。
通過(guò) Gate 門(mén)面
Gate
將會(huì)自動(dòng)通過(guò)檢測(cè)傳遞過(guò)來(lái)的類(lèi)參數(shù)來(lái)判斷使用哪一個(gè)策略類(lèi),因此,如果傳遞一個(gè) Post
實(shí)例給 denies
方法,相應(yīng)的,Gate
會(huì)使用 PostPolicy
來(lái)進(jìn)行動(dòng)作授權(quán):
<?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);
}
// 更新文章...
}
}
通過(guò) User 模型
User
模型的 can
和 cannot
方法將會(huì)自動(dòng)使用給定參數(shù)中有效的策略類(lèi)。這些方法提供了便利的方式來(lái)為應(yīng)用接收到的任意 User
實(shí)例進(jìn)行授權(quán):
if ($user->can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
Blade 模板中的使用
類(lèi)似的,Blade
指令@can
將會(huì)使用參數(shù)中有效的策略類(lèi):
@can('update', $post)
<!-- The Current User Can Update The Post -->
@endcan
通過(guò)幫助函數(shù) policy
全局的幫助函數(shù) policy
用于為給定類(lèi)實(shí)例接收策略類(lèi)。例如,我們可以傳遞一個(gè) Post
實(shí)例給幫助函數(shù) policy
來(lái)獲取相應(yīng)的 PostPolicy 類(lèi)的實(shí)例:
if (policy($post)->update($user, $post)) {
//
}
默認(rèn)情況下,Laravel
自帶的控制器基類(lèi) App\Http\Controllers\Controller
使用了 AuthorizesRequeststrait
,該 trait
提供了可用于快速授權(quán)給定動(dòng)作的 authorize
方法,如果授權(quán)不通過(guò),則拋出 HttpException
異常。
該 authorize
方法和其他多種授權(quán)方法使用方法一致,例如 Gate::allows
和$user->can()
。因此,我們可以這樣使用 authorize 方法快速授權(quán)更新 Post 的請(qǐng)求:
<?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);
// 更新文章...
}
}
如果授權(quán)成功,控制器繼續(xù)正常執(zhí)行;然而,如果 authorize
方法判斷該動(dòng)作授權(quán)失敗,將會(huì)拋出 HttpException
異常并生成帶 403 Not Authorized
狀態(tài)碼的 HTTP 響應(yīng)。正如你所看到的,authorize
方法是一個(gè)授權(quán)動(dòng)作、拋出異常的便捷方法。
AuthorizesRequeststrait
還提供了 authorizeForUser
方法用于授權(quán)非當(dāng)前用戶:
$this->authorizeForUser($user, 'update', $post);
自動(dòng)判斷策略類(lèi)方法
通常,一個(gè)策略類(lèi)方法對(duì)應(yīng)一個(gè)控制器上的方法,例如,在上面的 update 方法中,控制器方法和策略類(lèi)方法共享同一個(gè)方法名:update
。
正是因?yàn)檫@個(gè)原因,Laravel 允許你簡(jiǎn)單傳遞實(shí)例參數(shù)到 authorize
方法,被授權(quán)的權(quán)限將會(huì)自動(dòng)基于調(diào)用的方法名進(jìn)行判斷。在本例中,由于 authorize
在控制器的 update
方法中被調(diào)用,那么對(duì)應(yīng)的,PostPolicy
上 update
方法將會(huì)被調(diào)用:
/**
* 更新給定文章
*
* @param int $id
* @return Response
*/
public function update($id){
$post = Post::findOrFail($id);
$this->authorize($post);
// 更新文章...
}