授權(quán)
簡(jiǎn)介
laravel 除了提供開(kāi)箱即用的授權(quán)服務(wù),還提供了許多簡(jiǎn)單的方式來(lái)管理授權(quán)邏輯和資源的訪(fǎng)問(wèn)控制晨横。這些各式的方法和幫助函數(shù)便于你管理你的授權(quán)邏輯脉漏。我們將在本章中對(duì)其進(jìn)行一一的解讀谬擦。
定義能力
判斷一個(gè)用戶(hù)是否具有執(zhí)行給定動(dòng)作的能力的最簡(jiǎn)單的方式就是使用 Illuminate\Auth\Access\Gate
類(lèi)去定義相應(yīng)的能力。laravel 所提供的 AuthServiceProvider
類(lèi)是定義這些能力的推薦位置趾徽。讓我們來(lái)看個(gè)示例续滋,我們定義一個(gè) update-post
的能力,這個(gè)能力接收一個(gè)當(dāng)前 User
和一個(gè) Post
模型附较。在這個(gè)能力中吃粒,我們需要判斷用戶(hù)的 id
與 post 的 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
{
/**
* Register any application authentication / authorization services.
*
* @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;
});
}
}
注意上面的示例中我們并沒(méi)有檢查所給定的 $user
是否為 NULL
。這是因?yàn)楫?dāng)給定的用戶(hù)沒(méi)有經(jīng)過(guò)認(rèn)證或者用戶(hù)沒(méi)有經(jīng)過(guò) forUser
方法指定拒课,Gate
類(lèi)會(huì)自動(dòng)的為所有的能力返回 false
徐勃。
基于類(lèi)的能力
除了使用 Closures
的方式作為授權(quán)檢查的回調(diào)來(lái)注冊(cè)能力,你還可以通過(guò)傳遞類(lèi)中的方法來(lái)進(jìn)行能力的注冊(cè)早像,當(dāng)需要的時(shí)候僻肖,類(lèi)會(huì)通過(guò)服務(wù)容器來(lái)解析:
$gate->define('update-post', 'Class@method');
授權(quán)檢查攔截器
有時(shí)候,你可能需要向某些特殊用戶(hù)發(fā)放所有能力的通行證卢鹦,這個(gè)時(shí)候臀脏,你可以使用 before
方法來(lái)定義一個(gè)回調(diào),它會(huì)在所有的授權(quán)檢查之前運(yùn)行:
$gate->before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
如果 before
的回調(diào)函數(shù)返回一個(gè)非空的結(jié)果冀自,那么該結(jié)果將作為檢查的結(jié)果揉稚。
你也可以使用 after
方法來(lái)定義一個(gè)在每個(gè)能力授權(quán)檢查之后執(zhí)行的回調(diào)函數(shù),但是熬粗,你不能在這個(gè)回調(diào)函數(shù)內(nèi)修改檢查的結(jié)果:
$gate->after(function ($user, $ability, $result, $arguments) {
//
});
檢查能力
通過(guò) Gate 假面
一旦一個(gè)能力被定義完成搀玖,我們就可以通過(guò)多種方式來(lái)進(jìn)行能力的檢查。首先驻呐,我們可以使用 Gate
假面的 check
灌诅,allows
芳来,或者 denies
方法。所有的這些方法都會(huì)接收能力的名稱(chēng)猜拾,并且會(huì)把額外的參數(shù)傳遞給相應(yīng)能力的回調(diào)函數(shù)中即舌。你并不需要傳遞當(dāng)前的用戶(hù)到這些方法中,Gate
會(huì)自動(dòng)的前置當(dāng)前用戶(hù)到參數(shù)中并傳遞給能力的回調(diào)函數(shù)挎袜。所以當(dāng)我們檢查早前定義的 update-post
能力時(shí)顽聂,我們只需要傳遞 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
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update-post', $post)) {
abort(403);
}
// Update Post ...
}
}
當(dāng)然,allows
方法與 denies
相反盯仪,如果動(dòng)作被授權(quán)通過(guò)則返回 true
. check
方法就是 allows
方法的別名芜飘。
對(duì)指定的用戶(hù)檢查能力
如果你想要使用 Gate
假面來(lái)檢查非當(dāng)前經(jīng)授權(quán)通過(guò)用戶(hù)的其他用戶(hù)是否具備相應(yīng)的能力,你可以使用 forUser
方法:
if (Gate::forUser($user)->allows('update-post', $post)) {
//
}
傳遞多個(gè)參數(shù)
當(dāng)然磨总,能力的回調(diào)函數(shù)可以接收多個(gè)參數(shù):
Gate:define('delete-comment', function ($user, $post, $comment) {
//
});
如果你的能力需要接收多個(gè)參數(shù),你可以簡(jiǎn)單的通過(guò) Gate
假面的方法進(jìn)行傳遞一個(gè)經(jīng)多個(gè)參數(shù)所組成的數(shù)組:
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
通過(guò)用戶(hù)模型檢查能力
事實(shí)上笼沥,你可以通過(guò) User
模型的實(shí)例來(lái)檢查用戶(hù)的能力蚪燕。默認(rèn)的 laravel 的 App\User
模型使用了 Authorizable
trait,這個(gè)性狀包含兩個(gè)方法:can
和 cannot
奔浅。這兩個(gè)方法和 Gate
假面的 allows
和 denies
方法的用法相同馆纳。我們還使用上面曾使用過(guò)的例子,修改成如下:
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @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);
}
// Update Post...
}
}
當(dāng)然汹桦,can
方法與 cannot
的相反:
if ($request->user()->can('update-post', $post)) {
// Update Post...
}
在 Blade 模板中檢查能力
為了方便鲁驶,laravel 提供了 @can
Blade 指令來(lái)快速的檢查當(dāng)前授權(quán)的用戶(hù)是否具有指定的能力。比如:
<a href="/post/{{ $post->id }}">View Post</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan
你也可以通過(guò) @else
指令來(lái)配合 @can
指令:
@can('update-post', $post)
<!-- The Current User Can Update The Post -->
@else
<!-- The Current User Can't Update The Post -->
@endcan
在表單請(qǐng)求中檢查能力
你也可以通過(guò)使用表單請(qǐng)求(繼承自 Request舞骆,用于表單驗(yàn)證的請(qǐng)求類(lèi))中自定義的 authoriza
方法來(lái)驗(yàn)證 Gate
假面中定義的能力:
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$postId = $this->route('post');
return Cate::allows('update', Post::findOrFail($postId));
}
策略(Policies)
創(chuàng)建策略
為了不讓你把所有的授權(quán)邏輯全部放進(jìn) AuthServiceProvider
從而使應(yīng)用增長(zhǎng)為一個(gè)龐大而笨重的應(yīng)用钥弯。 laravel 允許你通過(guò) Policy
類(lèi)來(lái)分離你的授權(quán)邏輯。策略類(lèi)其實(shí)就是一個(gè)包含授權(quán)邏輯組的原生 PHP 類(lèi)督禽。
首先脆霎,讓我們來(lái)生成一個(gè)策略來(lái)管理我們的 Post
的授權(quán)。你可以通過(guò) make:policy
命令來(lái)生成一個(gè)策略狈惫。所生成的策略會(huì)存放在 app/Policies
目錄:
php artisan make:policy PostPolicy
注冊(cè)策略
一旦策略存在睛蛛,我們還需要在 Gate
類(lèi)中進(jìn)行注冊(cè)。在 AuthServiceProvider
中包含了一個(gè) policies
屬性胧谈,該屬性存放所有實(shí)體與策略間的映射忆肾。所以,我們需要將 Post
模型的策略指定到 PostPolice
類(lèi):
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
編寫(xiě)策略
一旦策略被生成和注冊(cè)后菱肖,我們就可以為所有能力的授權(quán)添加驗(yàn)證方法客冈。例如,讓我們?cè)?PostPolicy
類(lèi)中定義一個(gè) update
方法蔑滓,用來(lái)驗(yàn)證所給定的用戶(hù)是否具有 update
Post
的能力:
<?php
namespace App\Policies;
use App\Uesr;
use App\Post;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
你可以繼續(xù)在策略中添加其他所需進(jìn)行授權(quán)驗(yàn)證的方法郊酒。比如遇绞,你可以繼續(xù)為驗(yàn)證 Post
的各種動(dòng)作而定義 show
,destroy
燎窘,或者 addComment
方法摹闽。
注意:所有的策略類(lèi)都是通過(guò)服務(wù)容器解析而來(lái)。這意味著你可以使用類(lèi)型提示來(lái)在策略類(lèi)的構(gòu)造函數(shù)中進(jìn)行依賴(lài)注入所需要的依賴(lài)褐健。
攔截所有檢查
有時(shí)候付鹿,你可能需要發(fā)放給指定用戶(hù)具有所有能力的通行證,這時(shí)候蚜迅,你可以在策略類(lèi)中定義 before
方法舵匾。該方法會(huì)在策略中其他所有方法被執(zhí)行前運(yùn)行:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
如果 before
方法返回一個(gè)非空值,那么其結(jié)果將會(huì)用來(lái)作為授權(quán)驗(yàn)證的結(jié)果的判斷依據(jù)谁不。
檢查策略
策略類(lèi)的方法和基于授權(quán)回調(diào)的方法一樣通過(guò)相同的方式作為 Closure
被調(diào)用坐梯。你可以使用 Gate
假面,User
模型刹帕,@can
Blade 指令或者 policy
helper 來(lái)進(jìn)行授權(quán)的檢查吵血。
通過(guò) Gate 假面
Gate
會(huì)通過(guò)檢查傳遞給方法中參數(shù)的類(lèi)型來(lái)確定應(yīng)該使用哪一種策略。所以偷溺,如果我們傳遞 Post
實(shí)例到 denies
方法蹋辅,Gate
將會(huì)自動(dòng)的使用相對(duì)應(yīng)的 PostPolicy
來(lái)進(jìn)行授權(quán)驗(yàn)證:
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update', $post)) {
abort(403);
}
// Update Post...
}
}
通過(guò)用戶(hù)的模型
User
模型中的 can
和 cannot
方法也會(huì)在所給定參數(shù)可用時(shí)自動(dòng)匹配相應(yīng)的策略。這些方法提供了一種便利的方式去驗(yàn)證任意用戶(hù)實(shí)例是否具有所給定的能力:
if ($user->can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
通過(guò) Blade 模板
就像我們所期望的挫掏,@can
Blade 指令會(huì)在所給定參數(shù)可用時(shí)自動(dòng)匹配相應(yīng)的策略:
@can('update', $post)
<!-- The Current User Can update The Post -->
@endcan
```
**通過(guò)策略 Helper**
全局幫助方法 `policy` 可以通過(guò)所給定的類(lèi)解析相應(yīng)的 `Policy` 類(lèi)侦另。例如,我們可以傳遞一個(gè) `Post` 實(shí)例到 `policy` 幫助方法尉共,該方法會(huì)返回相應(yīng)的 `PostPolicy` 類(lèi):
```php
if (policy($post)->update($user, $post)) {
//
}
```
## 控制器授權(quán)
默認(rèn)的褒傅,在 laravel 中基于 `Ap\Http\Controllers\Controller` 的類(lèi)都引入了 `AuthorizesRequests` trait(性狀)。該性狀提供了 `authorize` 方法來(lái)快速的驗(yàn)證所給定的動(dòng)作是否有執(zhí)行的能力爸邢,如果不具備相應(yīng)的能力會(huì)拋出一個(gè) `HttpException` 樊卓。
`authorize` 方法共享了其它授權(quán)方法的簽證方式,如 `Gate::allows` 和 `$user->can()`杠河。那么碌尔,讓我們來(lái)使用 `authorize` 方法快速的鑒別一個(gè)請(qǐng)求是否具有更新 `Post` 的能力:
```php
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize('update', $post);
// Update Post...
}
}
```
如果這個(gè)動(dòng)作通過(guò)了授權(quán),控制器將繼續(xù)執(zhí)行下面的邏輯券敌。否則會(huì)自動(dòng)的拋出一個(gè) `HttpException` 錯(cuò)誤唾戚,這個(gè)錯(cuò)誤會(huì)生成一個(gè) `403 Not Authorized` 的 Http 響應(yīng)。就如你所看到的待诅, `authorize` 方法是一個(gè)非常方便的方法叹坦,它的方便之處就在于只使用一條語(yǔ)句執(zhí)行了授權(quán)的驗(yàn)證或拋出異常。
`AuthorizeRequests` trait 也提供了 `authorizeForUser` 方法來(lái)進(jìn)行非當(dāng)前用戶(hù)的用戶(hù)與給定能力的鑒權(quán):
```php
$this->authorizeForUser($user, 'update', $post);
```
**自動(dòng)的確定策略方法**
通常的卑雁,策略類(lèi)中的方法是與控制器中的方法相對(duì)應(yīng)的募书。比如在上面的 `update` 方法中绪囱,控制器的方法和策略的方法使用了相同的命名: `update`。
由于這個(gè)原因莹捡,laravel 允許你通過(guò)簡(jiǎn)單的傳遞一個(gè)實(shí)例參數(shù)到 `authorize` 方法鬼吵,在能力的鑒定中,laravel 會(huì)根據(jù)當(dāng)前方法的命名自動(dòng)的確定策略方法的調(diào)用篮赢。在上面的例子中齿椅,由于 `authorize` 方法是在控制器中的 `update` 方法中調(diào)用的,所以 `PostPolicy` 中的 `update` 將會(huì)被調(diào)用:
```php
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize($post);
// Update Post...
}
```