昨天在設定完身分驗證的架構後已經可以在路由上加 auth 中介層來驗證使用者的身分是否允許訪問。
Route::get('/flights', function () {
// 通過 admin 檢查的用戶才能進入路由
})->middleware('auth:admin');
不過除了只看身分外,Laravel 可以設定更詳細的權限檢查,像是檢查使用者是否擁有這筆 Todo ,是的話才能進行刪除等等。
定義權限的方式分為 Guard 跟 Policy ,兩者相較下 Policy 的架構會比較好管理跟維護,所以這裡介紹的重點會放在 Policy 上,Guard 就只稍微介紹點,不過其實 Policy 也是 Guard 的應用就是。
Guard 就像路由,一筆筆的定義某個行徑對應的檢查函式,一般都定義在 AuthServiceProvider 中。
/app/Providers/AuthServiceProvider.php
use App\Models\Todo;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
public function boot()
{
$this->registerPolicies();
Gate::define('update-todo', function (User $user, Todo $todo) {
return $user->id === $todo->user_id;
});
}
進行 update-todo 檢查時,會檢查傳入的 user 跟 todo ,若 todo 是屬於 user 的則回傳 true 表示權限許可,否則為 false。
use Illuminate\Support\Facades\Gate;
public function update(Request $request, Todo $todo)
{
if (! Gate::allows('update-todo', $todo)) {
abort(403);
}
// continue update process ...
}
接著就能用 Gate::allows 進行檢查,這邊要注意我們並沒有傳入 $user 參數,因為 Gate Facade 會自動從 Auth 取得目前登入的用戶代入。
如果想驗證的不是目前登入的使用者,加上 forUser 方法指定要驗證的使用者
if (! Gate::forUser($user)->allows('update-todo', $todo)) {
abort(403);
}
Policy 用於制定使用者對於某個 Model 的操作權限,其本質就是一組針對特定 Model 的 Gate 。
如果用指令創建 Model 的時候選擇了全餐 -a ,當中就會包含對應的 Policy ,如果沒有的話,就另外建立。
sail artisan make:policy TodoPolicy --model Todo
如果在建立 Policy 時有指定 Model 的話,就會預先產生一些方法像是 view,create,update 等。
Policy 產生後要在 AuthServiceProvider 中登記對特定 Model 進行權限檢查時要用哪個 Policy 。
/app/Providers/.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
App\Models\Todo::class => App\Policies\TodoPolicy::class,
];
//...
}
除了這裡之外也能動態的用 Gate::policy() 定義,例如:
$user = Auth::user();
if($user instanceof User){
\Gate::policy( Todo::class, TodoUserPolicy::class);
}else if($user instanceof Admin){
\Gate::policy( Todo::class, TodoAdminPolicy::class);
}
如果有複數身分 Model 可以考慮這麼做,不過一般都建議不要用多個身分驗證模型提高複雜度。
如果完全沒有註冊 Model 對應的 Policy, Guard 會試圖自動找出對應的 Policy ,尋找步驟如下:
可以看到用指令產生的 Policy 都符合上述的步驟,所以一般其實不用特地到 AuthServiceProvider 登記 Policy。
就跟寫 Guard 的 callback 函式相同,第一個參數傳入 User ,之後看要傳入哪個 Model。
public function update(User $user, Todo $todo)
{
return $user->id === $todo->user_id;
}
$user 要視驗證的身分標記對應的 Model ,一般來說是 User ,不過如果 Policy 用來對應 Admin 模型的驗證的話就要改成 Admin。
public function update(Admin $admin, Todo $todo)
{
return $admin->id === $todo->user_id;
}
權限檢查除了回傳 true / false 以外,也可以回傳帶有更多資訊的 Response 物件。注意是用 Auth 的 Response 。
use Illuminate\Auth\Access\Response;
public function update(User $user, Todo $todo)
{
return $user->id === $todo->user_id
? Response::allow()
: Response::deny('You do not own this post.');
}
回傳 Response 後如果用 can , allows 等方法,回傳還是一般的 true / false,要看到 Response 的內容的話要用 Gate 的 inspect 方法。
$response = Gate::inspect('update', $todo);
if ($response->allowed()) {
// The action is authorized...
} else {
echo $response->message();
}
Policy 中有特別的函式 before ,顧名思義會在進行任何檢查前執行,如果 before 回傳 null 以外的值的話該值會被當成這次檢查的回傳值。
public function before(User $user, $ability)
{
// 如果用戶是 admin 就跳過檢查一律回傳 true
if ($user->isAdministrator()) {
return true;
}
}
寫好之後終於可以用啦,用法跟 Guard 相似不過是從 User 出發進行檢查,並且不是用 allows 而是 can 跟 cannot 等方法檢查。
allows/ denies/ inspect 是 Guard 類別的方法
can/ cannot 是 Authorizable 類別(User 繼承的類別)的方法
注意別用錯了
if ($request->user()->cannot('update', $todo)) {
abort(403);
}
另外當驗證的方法不用除了 User 以外的資料時,需要帶入 Model 名稱才知道要找哪個 Policy 進行驗證。
if ($request->user()->cannot('create', Todo::class)) {
abort(403);
}