使用者授權
使用者授權
- ##簡介
- 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', 'App\Policies\PostPolicy@update'); }
#資源Gates
你也可以使用resource
#方法去一次性的定義多個Gate 方法:
Gate::resource('posts', 'App\Policies\PostPolicy');
上面的手動定義和以下的Gate 定義效果是相同的:
Gate::define('posts.view', 'App\Policies\PostPolicy@view'); Gate::define('posts.create', 'App\Policies\PostPolicy@create'); Gate::define('posts.update', 'App\Policies\PostPolicy@update'); Gate::define('posts.delete', 'App\Policies\PostPolicy@delete');
預設會定義view
, create
, update
, 和delete
方法。透過將一個陣列作為第三個參數傳給 resource
方法。你可以覆蓋或添加到預設的方法中。陣列的鍵定義能力的名稱,值定義方法的名稱。例如,下面的程式碼將建立兩個新的Gate 定義- posts.image
和posts.photo
:
Gate::resource('posts', 'PostPolicy', [ 'image' => 'updateImage', 'photo' => 'updatePhoto', ]);
授權動作
使用gates 來授權動作的時候, 你應該使用allows
或denies
方法。注意,不需要將目前已認證使用者傳遞給這些方法。 Laravel 會自動處理好已經認證通過的用戶,然後傳遞給gete 閉包函數:
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)) { // 用户不能更新... }
Gate 攔截檢查
#有時,你可能希望將所有能力授予特定使用者。所以你可以在所有其他授權檢查之前使用before
方法來定義執行的回呼:
Gate::before(function ($user, $ability) { if ($user->isSuperAdmin()) { return true; } });
如果before
回呼方法傳回的是非null 的結果,則結果將被視為檢查結果。
在每次授權檢查後你可以使用 after
方法定義要執行的回呼。但是,你不能從after
回呼方法修改授權檢查的結果:
Gate::after(function ($user, $ability, $result, $arguments) { // });
與before
檢查類似,如果after
回呼回傳非null 結果,則結果將被視為檢查結果。
建立策略
產生策略
策略是在特定模型或資源中組織授權邏輯的類別。例如,你的應用程式是一個博客,那麼你在創建或更新博客的時候,你可能會有一個 Post
模型和一個對應的 PostPolicy
來授權用戶動作。
你可以使用 artisan 指令
artisan command 中的 make:policy
artisan command 指令來產生策略。產生的策略將會放置在 app/Policies
目錄中。如果在你的應用程式中不存在這個目錄,那麼 Laravel 將會為你自動產生:
php artisan make:policy PostPolicy
make:policy
指令會產生一個空的策略類別。如果你想產生的類別包含基本的「CRUD」策略方法,你可以在執行指令的時候指定 --model
這個選項:
php artisan make:policy PostPolicy --model=Post
{tip} 所有的策略會透過Laravel 的 服務容器,來解析,允許你在策略構造器中對任何需要的依賴使用類型提示,並且自動注入。
註冊策略
一旦策略存在,它就需要註冊。新的 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(); // } }
#策略自動發現
只要模型和策略遵循標準的Laravel 命名約定,Laravel 就可以自動發現策略,而不是手動註冊模型策略。具體而言,策略必須位於包含模型的目錄下的 Policies
目錄中。因此,例如模型可以放在 app
目錄中,而策略可以放在 app/Policies
目錄中。此外,策略名稱必須與模型名稱匹配,並具有 Policy
後綴。因此,User
模型將對應於 UserPolicy
類別。
如果您想提供自己的策略發現邏輯,可以使用 Gate :: guessPolicyNamesUsing
方法註冊自訂回呼。通常,應該從應用程式的AuthServiceProvider
的boot
方法呼叫此方法:
use Illuminate\Support\Facades\Gate; Gate::guessPolicyNamesUsing(function ($modelClass) { // return policy class name... });
{note} 在
AuthServiceProvider
中顯式映射的任何策略都將優先於自動發現策略。
寫策略
#########################################################策略方法
一旦授權策略被註冊,你就可以為授權過後的每個動作添加方法。例如,我們在 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){ // }
訪客使用者
預設情況下,如果傳入的HTTP 請求不是經過驗證的使用者發起的,那麼所有的gates 和策略都會自動回傳false
。然而,你可以允許這些授權檢查透過宣告一個『可選的』類型提示或為使用者參數定義提供null
預設值,從而傳遞到你的gates 和策略:
<?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; } }#
策略過濾器
對特定用戶,你可能想要透過指定的策略授權所有動作。要達到這個目的,可以在策略中定義一個 before
方法。 before
方法會在策略中其它所有方法之前執行,這樣提供了一種方式來授權動作而不是指定的策略方法來執行判斷。這個功能最常見的場景是授權應用程式的管理員可以存取所有動作:
public function before($user, $ability){ if ($user->isSuperAdmin()) { return true; } }
如果你想拒絕某個使用者所有的授權,你應該在before
方法中回傳false
。如果傳回值是 null
,那麼授權會在這個策略中失敗。
{note} 策略類別的
before
方法不會被調用,如果該類別不包含與被檢查的功能名稱相符的方法。
使用策略授權動作
透過使用者模型
Laravel 內建的User
模型包含兩個有用的方法來授權動作: can
和cant
。這個 can
方法需要指定授權的動作以及相關的模型。例如,判斷是否授權一個使用者更新指定的Post
模型:
if ($user->can('update', $post)) { // }
如果指定模型的「策略已被註冊 」,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
中間件兩個參數。第一個參數是需要授權的動作名稱,第二個參數是我們希望傳遞給策略方法的路由參數。在這種情況下,我們使用了“ 隱式路由綁定”,一個 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 * @throws \Illuminate\Auth\Access\AuthorizationException */ public function update(Request $request, Post $post) { $this->authorize('update', $post); // 当前用户可以更新博客... } }
不需要指定模型的動作
和之前討論的一樣,一些動作,例如create
並不需要指定模型實例的動作。在這種情況下,你可以傳遞一個類別名稱給 authorize
方法。當授權這個動作時,這個類別名稱將被用來判斷使用哪個策略:
/** * 创建一个新的博客 * * @param Request $request * @return Response * @throws \Illuminate\Auth\Access\AuthorizationException */ public function create(Request $request){ $this->authorize('create', Post::class); // 当前用户可以新建博客... }
#授權資源控制器
如果你使用的是資源控制器,那麼你就可以在控制器建構方法裡使用authorizeResource
方法。這個方法會把適當 can
中間件附加到資源控制器對應的方法。
authorizeResource
方法接收範本類別名稱作為第一個參數,包含模型ID 的路由/ 請求參數的名稱作為其第二個參數:
<?php namespace App\Http\Controllers; use App\Post;use Illuminate\Http\Request; use App\Http\Controllers\Controller; class PostController extends Controller{ public function __construct() { $this->authorizeResource(Post::class, 'post'); } }
#{提示} 你可以使用帶有
--model
選項的make:policy
指令去快速產生基於給定模型的策略類別::php artisan make:policy PostPolicy --model=Post
。
透過Blade 模板
當寫Blade 模板時,你可能想要頁面的指定部分只顯示給授權存取指定動作的使用者。例如,你可能希望只展示更新的表單給有權更新部落格的使用者。在這樣情況下,你可以使用@can
和@cannot
等一系列指令:
@can('update', $post) <!-- The Current User Can Update The Post --> @elsecan('create', App\Post::class) <!-- The Current User Can Create New Post --> @endcan @cannot('update', $post) <!-- The Current User Can't Update The Post --> @elsecannot('create', App\Post::class) <!-- The Current User Can't Create New Post --> @endcannot
這些指令是寫@if
和@unless
語句的捷徑。 @can
和 @cannot
語句分別轉換為以下語句:
@if (Auth::user()->can('update', $post)) <!-- The Current User Can Update The Post --> @endif @unless (Auth::user()->can('update', $post)) <!-- The Current User Can't Update The Post --> @endunless#
不依賴模型的動作
與大多數其他授權方法一樣,如果動作不需要模型實例,則可以將類別名稱傳遞給@ can
和@ cannot
指令:
@can('create', App\Post::class) <!-- The Current User Can Create Posts --> @endcan @cannot('create', App\Post::class) <!-- The Current User Can't Create Posts --> @endcannot