* * * * *
[TOC]
## 簡介
除了提供開箱即用的?[用戶認證](http://www.hmoore.net/tonyyu/laravel_5_6/786216)?服務外, Laravel 還提供了一種更簡單的方式來處理用戶的授權動作。 類似用戶認證, Laravel 的用戶認證方法很簡單,并且提供了2種主要方式來實現用戶授權:gates 和策略。
可以把 gates 和策略比作路由和控制器。 Gates 提供了一個簡單的、基于閉包的方式來進行授權認證,策略和控制器類似,在特定的模型或者資源中通過分組來實現授權認證的邏輯。我們先來了解 gates 再去看策略。
在構建一個應用的時候,不用在專門使用 gates 或者只使用策略之間進行選擇。大部分應用很可能同時包含 gates 和策略, 并且能夠很好的進行工作。 Gates 大部分應用在模型和資源沒有關系的地方,比如查看管理員的面板。與之相反,策略應該在特定的模型或者資源中使用。
## Gates
### 編寫 Gates
Gates 是用來決定用戶是否授權執行給予動作的一個閉包函數,并且典型的做法就是在?`App\Providers\AuthServiceProvider`?中使用?`Gate`?來定義。 Gates 總是接收一個用戶實例作為第一個參數,并且可以接收可選參數,比如相關的 Eloquent 模型:
~~~
/**
* 注冊任意用戶認證、用戶授權服務。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
~~~
Gates 也可以使用類似控制器方法?`Class@method`?風格的回調字符串來定義:
~~~
/**
* 注冊任意用戶認證、用戶授權服務。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'PostPolicy@update');
}
~~~
#### 資源 Gates
你還可以使用?`resource`?方法去一次性的定義多個 Gate 方法:
~~~
Gate::resource('posts', 'PostPolicy');
~~~
上面的手動定義和以下的 Gate 定義效果是相同的:
~~~
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`?方法,你可以覆蓋或者添加到默認的方法中。數組的鍵定義能力的名稱,值定義方法的名稱。例如,下面的代碼將創建兩個新的 Gate 定義 -?`posts.image`?和?`posts.photo`:
~~~
Gate::resource('posts', 'PostPolicy', [
'image' => 'updateImage',
'photo' => 'updatePhoto',
]);
~~~
### 授權動作
使用 gates 來授權動作的時候, 你應該使用?`allows`?或者?`denies`?方法。 注意,你并不需要給已經認證通過的用戶傳遞這些方法。 Laravel 會自動處理好已經認證通過的用戶,然后傳遞給 gate 閉包函數:
~~~
if (Gate::allows('update-post', $post)) {
// 指定當前用戶可以進行更新...
}
if (Gate::denies('update-post', $post)) {
// 指定當前用戶不能進行更新...
}
~~~
如果你想判斷一個特定的用戶是否已經被授權訪問某個動作, 你可以使用在?`Gate`?facade中的?`forUser`?方法:
~~~
if (Gate::forUser($user)->allows('update-post', $post)) {
// 用戶可以更新...
}
if (Gate::forUser($user)->denies('update-post', $post)) {
// 用戶不能更新...
}
~~~
## 創建策略
### 生成策略
策略是在特定模型或者資源中組織授權邏輯的類。例如,你的應用是一個博客,那么你在創建或者更新博客的時候,你可能會有一個?`Post`?模型和一個對應的?`PostPolicy`?來授權用戶動作。
你可以使用[?artisan 命令?](http://www.hmoore.net/tonyyu/laravel_5_6/786238)中的?`make:policy`?命令來生成策略。生成的策略將放置在?`app/Policies`?目錄中。如果在你的應用中不存在這個目錄,那么 Laravel 將會為你自動生成:
~~~
php artisan make:policy PostPolicy
~~~
`make:policy`?命令會生成一個空的策略類。如果你想生成的類包含基本的 「CRUD」策略方法,你可以在執行命令的時候指定?`--model`?這個選項:
~~~
php artisan make:policy PostPolicy --model=Post
~~~
> {tip} 所有的策略會通過 Laravel 的[?服務容器?](http://www.hmoore.net/tonyyu/laravel_5_6/786056)來解析,允許你在策略構造器中對任何需要的依賴使用類型提示,并且自動注入。
### 注冊策略
一旦策略存在,它就需要進行注冊。新的 Laravel 應用中包含的?`AuthServiceProvider`?有一個?`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();
//
}
}
~~~
## 編寫策略
### 策略方法
一旦授權策略被注冊,你就可以為授權過后的每個動作添加方法。比如,我們在?`PostPolicy`?中定義一個?`update`?方法,它會判斷指定的?`User`?是否可以更新指定的?`Post`?實例。
`update`?方法接收?`User`?和?`Post`?實例作為參數,并且應該返回?`true`?或者?`false`?來表明用戶是否被授權更新指定的?`Post`?。所以在這個例子中,我們需要判斷用戶的?`id`?是否和 post 中的?`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;
}
}
~~~
你可以繼續為這個授權策略定義額外的方法。比如,你可以定義?`view`?或者?`delete`?方法來授權?`Post`?的多種行為,還可以為自定義的策略方法起一個你自己喜歡的名字。
> {tip} 如果在 Artisan 控制臺生成策略時使用?`--model`?選項, 它會包含進去?`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`?方法中返回 。如果 返回值是`null`?, 那么授權會在這個策略中失敗。
> {note} 策略類的?`before`?方法不會被調用,如果該類不包含與被檢查的功能名稱相符的方法。
## 使用策略授權動作
### 通過用戶模型
Lavarel內置的?`User`?模型包含兩個有用的方法來授權動作:`can`?和?`cant`.?`can`?方法需要指定授權的動作以及相關的模型。例如,判定是否授權一個用戶更新指定的?`Post`?模型:
~~~
if ($user->can('update', $post)) {
//
}
~~~
如果指定模型的?[策略已被注冊](http://www.hmoore.net/tonyyu/laravel_5_6/786218#_120)?,`can`?方法會自動調用合適的策略并返回一個 boolean 值。如果沒有策略注冊到這個模型,`can`?方法會嘗試調用和指定動作名稱相匹配的基于閉包的Gate。
#### 不需要指定模型的動作
記住, 一些動作,比如?`create`?并不需要指定模型實例。在這種情況下,可傳遞一個類名給?`can`?方法。這個類名將被用來判定使用哪種策略授權動作:
~~~
use App\Post;
if ($user->can('create', Post::class)) {
// 執行相關策略中的「create」方法...
}
~~~
### 通過中間件
Laravel 包含一個可以在請求到達路由或者控制器之前就進行動作授權的中間件。默認的,?`Illuminate\Auth\Middleware\Authorize`?中間件被指定到你的?`App\Http\Kernel`?類中的?`can`?鍵上。讓我們用一個授權用戶更新博客的例子來講解一下?`can`?這個中間件的使用:
~~~
use App\Post;
Route::put('/post/{post}', function (Post $post) {
// 當前用戶可以進行更新操作
})->middleware('can:update,post');
~~~
在這個例子中,我們傳給了?`can`?兩個參數。第一個參數是需要授權的動作的名稱,第二個參數是我們希望傳遞給策略方法的路由參數。這里我們使用了[?隱式路由綁定?](http://www.hmoore.net/tonyyu/laravel_5_6/786088#_290), 一個?`Post`?會被傳遞給策略方法。如果用戶不被授權訪問指定的動作,這個中間件將會生成帶有?`403`?狀態碼的 HTTP 響應。
#### 不需要指定模型的動作
同樣,一些像?`create`?這樣的動作可能不需要模型實例。在這種情況下,你可以傳遞一個類名給中間件。當授權這個動作時這個類名將被用來判斷使用哪個策略:
~~~
Route::post('/post', function () {
// 當前用戶可以進行創建操作
})->middleware('can:create,App\Post');
~~~
### 通過控制器輔助函數
除了在?`User`?模型中提供輔助方法以外,Laravel 也為繼承?`App\Http\Controllers\Controller`?這個基類的控制器提供了一個有用的?`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 模板
當編寫 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
~~~
- 前言
- 翻譯說明
- 發行說明
- 升級指南
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- Homestead
- Valet
- 部署
- 核心架構
- 請求周期
- 服務容器
- 服務提供者
- Facades
- Contracts
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- URL
- Session
- 表單驗證
- 錯誤
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全相關
- 用戶認證
- Passport OAuth 認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- API 資源
- 序列化
- 測試相關
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Horizon
- Scout 全文搜索
- Socialite 社會化登錄