# Laravel 的用戶授權系統
- [簡介](#introduction)
- [Gates](#gates)
- [編寫 Gates](#writing-gates)
- [授權動作](#authorizing-actions-via-gates)
- [創建策略](#creating-policies)
- [生成策略](#generating-policies)
- [注冊策略](#registering-policies)
- [編寫策略](#writing-policies)
- [策略方法](#policy-methods)
- [不使用模型方法](#methods-without-models)
- [策略過濾器](#policy-filters)
- [使用策略授權動作](#authorizing-actions-using-policies)
- [通過用戶模型](#via-the-user-model)
- [通過中間件](#via-middleware)
- [通過控制器輔助函數](#via-controller-helpers)
- [通過 Blade 模板](#via-blade-templates)
<a name="introduction"></a>
## 簡介
除了內置開箱即用的 [用戶認證](/docs/{{version}}/authentication) 服務外,Laravel 還提供一種更簡單的方式來處理用戶授權動作。類似用戶認證,Laravel 有 2 種主要方式來實現用戶授權:gates 和策略。
可以把 gates 和策略類比于路由和控制器。 Gates 提供了一個簡單的、基于閉包的方式來授權認證。策略則和控制器類似,在特定的模型或者資源中通過分組來實現授權認證的邏輯。我們先來看看 gates,然后再看策略。
在你的應用中,不要將 gates 和策略當作相互排斥的方式。大部分應用很可能同時包含 gates 和策略,并且能很好的工作。Gates 大部分應用在模型和資源無關的地方,比如查看管理員的面板。與之相反,策略應該用在特定的模型或者資源中。
<a name="gates"></a>
## Gates
<a name="writing-gates"></a>
### 編寫 Gates
Gates 是用來決定用戶是否授權執行給定的動作的閉包函數,并且典型的做法是在 `App\Providers\AuthServiceProvider` 類中使用 `Gate` facade 定義。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` 風格的回調字符串來定義,比如控制器:
/**
* Register any authentication / authorization services.
*
* @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',
]);
<a name="authorizing-actions-via-gates"></a>
### 授權動作
使用 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)) {
// 指定用戶不能更新博客...
}
<a name="creating-policies"></a>
## 創建策略
<a name="generating-policies"></a>
### 生成策略
策略是在特定模型或者資源中組織授權邏輯的類。例如,如果你的應用是一個博客,會有一個 `Post` 模型和一個相應的 `PostPolicy` 來授權用戶動作,比如創建或者更新博客。
可以使用 `make:policy` [artisan 命令](/docs/{{version}}/artisan) 來生成策略。生成的策略將放置在 `app/Policies` 目錄。如果在你的應用中不存在這個目錄,那么 Laravel 會自動創建:
php artisan make:policy PostPolicy
`make:policy` 會生成空的策略類。如果希望生成的類包含基本的「CRUD」策略方法, 可以在使用命令時指定 `--model` 選項:
php artisan make:policy PostPolicy --model=Post
> {tip} 所有授權策略會通過 Laravel [服務容器](/docs/{{version}}/container) 解析,意指你可以在授權策略的構造器對任何需要的依賴使用類型提示,它們將會被自動注入。
<a name="registering-policies"></a>
### 注冊策略
一旦該授權策略存在,需要將它進行注冊。新的 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();
//
}
}
<a name="writing-policies"></a>
## 編寫策略
<a name="policy-methods"></a>
### 策略方法
一旦授權策略被生成且注冊,我們就可以為授權的每個動作添加方法。例如,讓我們在 `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` 動作。
<a name="methods-without-models"></a>
### 不包含模型方法
一些策略方法只接受當前認證通過的用戶作為參數而不用傳入授權相關的模型實例。最普遍的應用場景就是授權 `create` 動作。例如,如果正在創建一篇博客,你可能希望檢查一下當前用戶是否有權創建博客。
當定義一個不需要傳入模型實例的策略方法時,比如 `create` 方法,你需要定義這個方法只接受已授權的用戶作為參數:
/**
* 判斷指定用戶是否可以創建博客。
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
<a name="policy-filters"></a>
### 策略過濾器
對特定用戶,你可能希望通過指定的策略授權所有動作。 要達到這個目的,可以在策略中定義一個 `before` 方法。`before` 方法會在策略中其它所有方法之前執行,這樣提供了一種方式來授權動作而不是指定的策略方法來執行判斷。這個功能最常見的場景是授權應用的管理員可以訪問所有動作:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
如果你想拒絕用戶所有的授權,你應該在 `before` 方法中返回 `false`。如果返回的是 `null`,則通過其它的策略方法來決定授權與否。
<a name="authorizing-actions-using-policies"></a>
## 使用策略授權動作
<a name="via-the-user-model"></a>
### 通過用戶模型
Laravel 應用內置的 `User` 模型包含 2 個有用的方法來授權動作:`can` 和 `cant`。`can` 方法需要指定授權的動作和相關的模型。例如,判定一個用戶是否授權更新指定的 `Post` 模型:
if ($user->can('update', $post)) {
//
}
如果指定模型的 [策略已被注冊](#registering-policies),`can` 方法會自動調用核實的策略方法并且返回 boolean 值。如果沒有策略注冊到這個模型,`can` 方法會嘗試調用和動作名相匹配的基于閉包的 Gate。
#### 不需要指定模型的動作
記住,一些動作,比如 `create` 并不需要指定模型實例。在這種情況下,可傳遞一個類名給 `can` 方法。當授權動作時,這個類名將被用來判斷使用哪個策略:
use App\Post;
if ($user->can('create', Post::class)) {
// 執行相關策略中的「create」方法...
}
<a name="via-middleware"></a>
### 通過中間件
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` 中間件 2 個參數。第一個是需要授權的動作的名稱,第二個是我們希望傳遞給策略方法的路由參數。這里因為使用了 [隱式模型綁定](/docs/{{version}}/routing#implicit-binding),一個 `Post` 會被傳遞給策略方法。如果用戶不被授權訪問指定的動作,這個中間件會生成帶有 `403` 狀態碼的 HTTP 響應。
#### 不需要指定模型的動作
同樣的,一些動作,比如 `create`,并不需要指定模型實例。在這種情況下,可傳遞一個類名給中間件。當授權動作時,這個類名將被用來判斷使用哪個策略:
Route::post('/post', function () {
// 當前用戶可以創建博客...
})->middleware('can:create,App\Post');
<a name="via-controller-helpers"></a>
### 通過控制器輔助函數
除了在 `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);
// 當前用戶可以新建博客...
}
<a name="via-blade-templates"></a>
### 通過 Blade 模板
當編寫 Blade 模板時,你可能希望頁面的指定部分只展示給允許授權訪問指定動作的用戶。 例如,你可能希望只展示更新表單給有權更新博客的用戶。這種情況下,你可以直接使用 `@can` 和 `@cannot` 指令。
@can('update', $post)
<!-- 當前用戶可以更新博客 -->
@elsecan('create', $post)
<!-- 當前用戶可以新建博客 -->
@endcan
@cannot('update', $post)
<!-- 當前用戶不可以更新博客 -->
@elsecannot('create', $post)
<!-- 當前用戶不可以新建博客 -->
@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
## 譯者署名
| 用戶名 | 頭像 | 職能 | 簽名 |
|---|---|---|---|
| [@iwzh](https://github.com/iwzh) | <img class="avatar-66 rm-style" src="https://dn-phphub.qbox.me/uploads/avatars/3762_1456807721.jpeg?imageView2/1/w/200/h/200"> | 翻譯 | 碼不能停 [@iwzh](https://github.com/iwzh) at Github |
---
> {note} 歡迎任何形式的轉載,但請務必注明出處,尊重他人勞動共創開源社區。
>
> 轉載請注明:本文檔由 Laravel China 社區 [laravel-china.org](https://laravel-china.org) 組織翻譯,詳見 [翻譯召集帖](https://laravel-china.org/topics/5756/laravel-55-document-translation-call-come-and-join-the-translation)。
>
> 文檔永久地址: https://d.laravel-china.org
- 說明
- 翻譯說明
- 發行說明
- 升級說明
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- HomeStead
- Valet
- 核心架構
- 請求周期
- 服務容器
- 服務提供者
- 門面(Facades)
- Contracts
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- 重定向
- Session
- 表單驗證
- 錯誤與日志
- 前端開發
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全
- 用戶認證
- API認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- API 資源
- 序列化
- 測試
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Horizon
- Passport OAuth 認證
- Scout 全文搜索
- Socialite 社交化登錄
- 交流說明