[TOC]
### **1、簡介**
除了提供開箱即用的[認證服務](http://laravelacademy.org/post/3074.html)之外,[Laravel](http://laravelacademy.org/tags/laravel "View all posts in Laravel")?還提供了一個簡單的方式來管理[授權](http://laravelacademy.org/tags/%e6%8e%88%e6%9d%83 "View all posts in 授權")邏輯以便控制對資源的訪問權限。在 Laravel 中,有多種方法和[輔助函數](http://laravelacademy.org/tags/%e8%be%85%e5%8a%a9%e5%87%bd%e6%95%b0 "View all posts in 輔助函數")來協助你管理授權邏輯,本[文檔](http://laravelacademy.org/tags/%e6%96%87%e6%a1%a3 "View all posts in 文檔")將會一一覆蓋這些方法。
### **2、定義權限(Abilities)**
判斷[用戶](http://laravelacademy.org/tags/%e7%94%a8%e6%88%b7 "View all posts in 用戶")是否有權限執行給定動作的最簡單方式就是使用?`Illuminate\Auth\Access\[Gate](http://laravelacademy.org/tags/gate "View all posts in Gate")`?類來定義一個“權限”。我們在`AuthServiceProvider`?中定義所有權限,例如,我們來定義一個接收當前?`User`?和?`Post`?[模型](http://laravelacademy.org/post/2995.html)的`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)
{
parent::registerPolicies($gate);
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
}
}
~~~
注意我們并沒有檢查給定?`$user`?是否為?`NULL`,當用戶未經過登錄認證或者用戶沒有通過?`forUser`?方法指定,`Gate`?會自動為所有權限返回?`false`。
##### **基于類的權限**
除了注冊授權回調閉包之外,還可以通過傳遞包含權限類名和類方法的方式來注冊權限方法,當需要的時候,該類會通過[服務容器](http://laravelacademy.org/post/2910.html)進行解析:
~~~
$gate->define('update-post', 'PostPolicy@update');
~~~
##### **攔截認證檢查**
有時候,你可能希望對指定用戶授予所有權限,在這種場景中,需要使用?`before`?方法定義一個在所有其他授權檢查之前運行的回調:
~~~
$gate->before(function?($user,?$ability)?{
if?($user->isSuperAdmin())?{
return?true;
}
});
~~~
如果?`before`?回調返回一個非空結果,那么該結果則會被當做檢查的結果。
你也可以使用?`after`?方法定義一個在所有其他授權檢查之后運行的回調,所不同的是,在?`after`?回調中你不能編輯授權檢查的結果:
~~~
$gate->after(function?($user,?$ability,?$result,?$arguments)?{
//
});
~~~
### **3、檢查權限(Abilities)**
#### **通過 Gate 門面**
權限定義好之后,可以使用多種方式來“檢查”。首先,可以使用?`Gate`?[門面](http://laravelacademy.org/post/2920.html)的?`check`,?`allows`, 或者`denies`?方法。所有這些方法都接收權限名和傳遞給該權限回調的參數作為參數。你不需要傳遞當前用戶到這些方法,因為?`Gate`?會自動附加當前用戶到傳遞給回調的參數,因此,當檢查我們之前定義的?`update-post`?權限時,我們只需要傳遞一個`Post`?實例到?`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);
}
// 更新文章...
}
}
~~~
當然,`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?[模型](http://laravelacademy.org/tags/%e6%a8%a1%e5%9e%8b "View all posts in 模型")**
還可以通過?`User`?模型實例來檢查權限。默認情況下,Laravel 的?`App\User`?模型使用一個?`Authorizable`?trait來提供兩種方法:`can`?和?`cannot`。這兩個方法的功能和?`Gate`?門面上的?`allows`?和?`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)) {
// 更新文章...
}
~~~
#### **在?****[Blade](http://laravelacademy.org/tags/blade "View all posts in Blade")?****模板引擎中檢查**
為了方便,Laravel 提供了 Blade 指令?`@can`?來快速檢查當前用戶是否有指定權限。例如:
~~~
<a href="/post/{{ $post->id }}">View Post</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan
~~~
你還可以將?`@can`?指令和?`@else`?指令聯合起來使用:
~~~
@can('update-post', $post)
<!-- The Current User Can Update The Post -->
@else
<!-- The Current User Can't Update The Post -->
@endcan
~~~
#### **在[表單](http://laravelacademy.org/tags/%e8%a1%a8%e5%8d%95 "View all posts in 表單")請求中檢查**
你還可以選擇在表單請求的?`authorize`?方法中使用?`Gate`?定義的權限。例如:
~~~
/**
* 判斷請求用戶是否經過授權
*
* @return bool
*/
public function authorize(){
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
~~~
### **4、[策略](http://laravelacademy.org/tags/%e7%ad%96%e7%95%a5 "View all posts in 策略")類(Policies)**
#### **創建策略類**
由于在?`AuthServiceProvider`?中定義所有的授權邏輯將會變得越來越臃腫笨重,尤其是在大型應用中,所以 Laravel 允許你將授權邏輯分割到多個“策略”類中,策略類是原生的PHP類,基于授權資源對授權邏輯進行分組。
首先,讓我們生成一個策略類來管理對?`Post`?模型的授權,你可以使用 Artisan 命令?`make:policy`?來生成該策略類。生成的策略類位于?`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\Contracts\Auth\Access\Gate?as?GateContract;
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);
? ? }
}
~~~
#### **編寫策略類**
策略類生成和注冊后,我們可以為授權的每個權限添加方法。例如,我們在?`PostPolicy`?中定義一個?`update`?方法,該方法判斷給定?`User`?是否可以更新某個?`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;
}
}
~~~
你可以繼續在策略類中為授權的權限定義更多需要的方法,例如,你可以定義?`show`,?`destroy`, 或者?`addComment`?方法來認證多個?`Post`?動作。
> 注意:所有策略類都通過服務容器進行解析,這意味著你可以在策略類的構造函數中類型提示任何依賴,它們將會自動被注入。
##### **攔截所有檢查**
有時候,你可能希望對指定用戶授予所有權限,在這種場景中,需要使用?`before`?方法定義一個在所有其他授權檢查之前運行的回調:
~~~
$gate->before(function?($user,?$ability)?{
if?($user->isSuperAdmin())?{
return?true;
}
});
~~~
如果?`before`?回調返回一個非空結果,那么該結果則會被當做檢查的結果。
#### **檢查策略**
策略類方法的調用方式和基于授權回調的閉包一樣,你可以使用?`Gate`?門面,`User`?模型,`@can`?指令或者輔助函數`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 模板中使用**
類似的,Blade 指令?`@can`?將會使用參數中有效的策略類:
~~~
@can('update', $post)
<!-- The Current User Can Update The Post -->
@endcan
~~~
##### **通過輔助函數 policy**
全局的輔助函數?`policy`?用于為給定類實例接收策略類。例如,我們可以傳遞一個?`Post`?實例給幫助函數`policy`?來獲取相應的?`PostPolicy`?類的實例:
~~~
if (policy($post)->update($user, $post)) {
//
}
~~~
### **5、[控制器](http://laravelacademy.org/tags/%e6%8e%a7%e5%88%b6%e5%99%a8 "View all posts in 控制器")授權**
默認情況下,Laravel 自帶的控制器基類?`App\Http\Controllers\Controller`?使用了?`AuthorizesRequests`?trait,該 trait 提供了可用于快速授權給定動作的?`authorize`?方法,如果授權不通過,則拋出?`HttpException`?異常。
該?`authorize`?方法和其他多種授權方法使用方法一致,例如?`Gate::allows`?和?`$user->can()`。因此,我們可以這樣使用?`authorize`?方法快速授權更新?`Post`?的請求:
~~~
<?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`?方法是一個授權動作、拋出異常的便捷方法。
`AuthorizesRequests`?trait還提供了?`authorizeForUser`?方法用于授權非當前用戶:
~~~
$this->authorizeForUser($user, 'update', $post);
~~~
##### **自動判斷策略類方法**
通常,一個策略類方法對應一個控制器上的方法,例如,在上面的?`update`?方法中,控制器方法和策略類方法共享同一個方法名:`update`。
正是因為這個原因,Laravel 允許你簡單傳遞實例參數到?`authorize`?方法,被授權的權限將會自動基于調用的方法名進行判斷。在本例中,由于?`authorize`?在控制器的?`update`?方法中被調用,那么對應的,`PostPolicy`?上?`update`方法將會被調用:
~~~
/**
*?更新給定文章
*
*?@param? int? $id
*?@return?Response
*/
public?function?update($id){
$post?=?Post::findOrFail($id);
$this->authorize($post);
//?更新文章...
}
~~~
- 序言
- 發行版本說明
- 升級指南
- 貢獻代碼
- 開始
- 安裝
- 配置
- Laravel Homestead
- 基礎
- HTTP 路由
- HTTP 中間件
- HTTP 控制器
- HTTP 請求
- HTTP 響應
- 視圖
- Blade 模板引擎
- 架構
- 一次請求的生命周期
- 應用目錄結構
- 服務提供者
- 服務容器
- 門面(Facades)
- 數據庫
- 起步
- 查詢構建器
- 遷移
- 填充數據
- Eloquent ORM
- 起步
- 關聯關系
- 集合
- 訪問器&修改器
- 序列化
- 服務
- 用戶認證
- 用戶授權
- Artisan Console
- 訂閱支付實現:Laravel Cashier
- 緩存
- 集合
- 集成前端資源:Laravel Elixir
- 加密
- 錯誤&日志
- 事件
- 文件系統/云存儲
- 哈希
- 輔助函數
- 本地化
- 郵件
- 包開發
- 分頁
- Redis
- 隊列
- Session
- Envoy Task Runner
- 任務調度
- 測試
- 驗證
- 新手入門指南
- 簡單任務管理系統
- 帶用戶功能的任務管理系統