簡介
除了開箱提供的認證服務,在 Laravel 中對使用者授權某個資源的操作許可權也非常容易實現。Laravel 授權的方式主要分兩種:Gates 和策略(Policies)。
Gates 和策略的關係像路由和控制器的。Gates 提供基於路由的授權方式;而策略將針對某個資源的操作邏輯組織起來,放在一個地方管理。我們會先介紹 Gates,然後再介紹策略。
我們不需要在「專案裡的授權,是選擇使用 Gates,還是選擇使用策略呢?」這個問題上糾結。許多專案裡,都會混用這兩種授權方式,而且執行良好。基本上,使用 Gates 的地方都是資源不相關的,比如檢視管理員皮膚頁;相反,策略是資源相關的,當你是具體授權某個資源的操作許可權時,使用策略是沒錯啦。
Gates
我們通常在 App\Provider\AuthServiceProvider
類中使用 Gate
門面來定義 Gates。已經說過,Gates 是基於閉包判斷使用者是否有操作許可權的,回撥閉包的第一個引數是使用者例項,額外可選的第二個引數是相關的 Eloquent Model 例項:
/**
* Register any authentication / authorization services.
*
* @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 定義中的 'update-post'
叫「能力」(Ability)。
資源 Gates
使用 Gate::resource
方法一次定義多個 Gates:
Gate::resource('posts', 'PostPolicy');
這一句相當於手動定義了下面的 Gates:
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
方法的第三個陣列引數。陣列的 Key 是能力名,Value 是方法名。下面的例子裡,我們定義了兩個 Gates 能力:posts.image
和 posts.photo
:
Gate::resource('photos', 'PostPolicy', [
'image' => 'updateImage',
'photo' => 'updatePhoto',
]);
授權操作
使用 Gates 授權操作,是用到 allows
和 denies
方法。需要注意的是,你不需要傳遞當前的認證使用者例項給這兩個方法,因為 Laravel 會自動為我們的 Gates 傳遞使用者例項的:
if (Gate::allows('update-post', $post) {
// The current user can update the post...
})
if (Gate::denies('update-post', $post)) {
// The current user can't update the post...
}
如果需要判斷一個指定使用者賬號是否有進行某個操作的許可權,那麼在使用 forUser
方法吧!
if (Gate::forUser($user)->allows('update-post', $post) {
// The current user can update the post...
})
if (Gate::forUser($user)->denies('update-post', $post)) {
// The current user can't update the post...
}
建立策略
生成策略
策略是針對特定模型或資源、方便得組織它們的授權邏輯在一個地方的類。例如,在一個部落格系統中,我們用 Post
模型對應的策略類 PostPolicy
(注意,這裡的跟上面的不一樣)來授權認證使用者建立和更新部落格相關的許可權。
你可以使用 Artisan 命令 make:policy
生成策略。生成的策略放在 app/Policies
目錄下,如果這個目錄不存在,它會在第一次執行此命令時建立:
php artisan make:policy PostPolicy
make:policy
命令會生成一個空的策略類。如果需要生成帶有基本「CRUD」策略方法的策略類。那麼在執行 make:policy
命令是跟上 --model
選擇吧:
php artisan make:policy PostPolicy --model=Post
提示:-D 所有的策略都是通過 Laravel 的服務容器解析的,因此你可以在策略類的建構函式裡新增任何你需要依賴注入的例項。
註冊策略
策略類生成完畢後需要註冊它。AuthServiceProvider
的 policies
陣列屬性就是幹這個的,在這裡為你的 Eloquent Model 新增對應的策略類對映。註冊後,Laravel 在對給定 Model 資源授權的時候,就知道來找哪個策略類了:
<?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
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
寫策略類
策略方法
策略類註冊完畢後,再來看看怎樣為每個操作新增對應的策略方法。在接下來的例子裡,我們為 PostPolicy
定義一個 update
方法,用來判斷是否指定的使用者擁有更新部落格的許可權。
update
方法接受兩個引數:User
例項和 Post
例項,我們最終返回 true
或者 false
來說明使用者是否有許可權進行操作。在此,我們使用的是使用者的 id
欄位來匹配部落格的 user_id
欄位的。
<?php
namespace App\Policies;
use App\User;
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;
}
}
你可以繼續為你的其他操作新增對應策略方法,比如 view
或者 delete
啦,還有一點,策略方法的名字你是可以自由取的。
提示:-D 在控制檯執行使用
--model
選項生成策略類的時候,會自動生成針對增刪改查操作的create
、delete
、update
和view
策略方法。
無 Model 策略方法
「無模型策略方法」是指策略方法裡不需要傳入模型例項的情況。最常見的場景就是建立操作,我們假設它對應 create
策略方法。例如,你可能會判斷一個使用者是否有建立部落格的許可權,這時就不需要有部落格例項物件了,只要給個認證使用者就行了:
/**
* Determine if the given user can create posts.
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
策略過濾
對於一些使用者,我們會碰到無論如何要給他整個策略類中所有方法的操作許可權(想想管理員)。為了實現這個功能,在策略類中定義一個 before
方法就 OK。before
方法會在所有策略方法呼叫之前呼叫:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
如果要拒絕使用者的所有操作許可權,直接返回 false
。如果返回 null
的話,就跟沒設定 before
方法一樣一樣的。
重點提示( ⊙ o ⊙ ) 如果 Gates 能力對應的策略方法在策略類中未定義,
before
方法是不會被呼叫的。
使用策略授權操作
通過 User Model
Laravel 中的 User Model 中包含兩個有用的授權方法:can
和 cant
。can
方法接收兩個引數,第一個引數是操作名,第二個就是相關 Model 例項了。下面的例子裡,我們判斷使用者是否有更新指定 Post
Model 例項的許可權:
if ($user->can('update', $post)) {
//
}
can
方法會自動呼叫正確的策略類的 update
方法並返回一個布林值。如果沒有知道的話,就會去 Gates 中找有沒有一個能力叫 update
的。
無 Model 操作
前面已經說過,對於建立這一類授權呢,定義時是不需要給 Model 例項的。那麼在使用這樣的一類授權方法時,怎麼寫呢?這時候,給 can
方法傳遞對應 Model 類名就 OK。
use App\Post;
if ($user->can('create'), Post::class) {
// Executes the "create" method on the relevant policy...
}
通過中介軟體
Laravel 提供了一個授權操作的中介軟體(Illuminate\Auth\Middleware\Authorize
),它會在你的請求到大路由或者控制器的時候,判斷使用者是否有相關許可權。這個中介軟體已經在 App\Http\Kernel
類中設定了,並且賦予了 can
這個 Key。我們來看下,針對更新部落格的操作,我們們怎麼去使用它:
use App\Post;
Route::put('/posts/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
can
冒號後面用逗號隔開的就是兩個引數。第一個引數是操作名,第二個引數是我們傳遞給策略方法的路由引數,在這裡我們使用了隱式模型繫結,所以一個 Post
模型例項會被傳遞給策略方法。如果使用者授權未通過,一個帶有 403
狀態碼的 HTTP 響應就會通過該中介軟體生成。
無 Model 操作
一樣的,對於不需要給 Model 例項的授權操作(比如建立)判斷方法,我們需要給中介軟體傳遞的第二個引數是類名:
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
通過控制器輔助函式
除了 User
Model 提供的輔助函式,Laravel 為所有繼承了 App\Http\Controllers\Controller
基類的控制器都提供了 auhtorize
輔助方法。類似 can
方法,authorize
方法接受操作名和相關 Model 例項物件作為引數。如果操作經過驗證未授權,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
{
/**
* Update the given blog post.
*
* @param Request $request
* @param Post $post
* @return Response
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// The current user can update the blog post...
}
}
無 Model 操作
再一次,對於不需要給 Model 例項的授權操作判斷方法,我們需要給 authorize
方法傳遞的第二個引數是類名:
/**
* Create a new blog post.
*
* @param Request $request
* @return Response
*/
public function create(Request $request)
{
$this->authorize('create', Post::class);
// The current user can create blog posts...
}
通過 Blade 模板
在 Blade 模板中,提供了兩個用來判斷使用者是否具有操作許可權的指令:@can
和 cannot
。它們在判斷頁面是否該顯示一些內容上比較有用,使用方式如下:
@can('update', $post)
<!-- The Current User Can Update The Post -->
@elsecan('create', $post)
<!-- The Current User Can Create New Post -->
@endcan
@cannot('update', $post)
<!-- The Current User Can't Update The Post -->
@elsecannot('create', $post)
<!-- The Current User Can't Create New Post -->
@endcannot
上面的指令其實是 @if
和 @unless
的快捷形式的寫法。等同於
@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
無 Model 操作
你可以用 @can
和 @cannot
指令處理無 Model 操作的許可權判斷場景,需要傳遞的是相關類名:
@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
本作品採用《CC 協議》,轉載必須註明作者和本文連結