授權
簡介
除了提供內建的認證服務,Laravel 也提供了一種能依給定資源來授權使用者的簡單功能。舉例來說,雖然使用者已登入,但這個使用者可能未被授權可更新或刪除網站所管理的特定的 Eloquent Model 或資料庫記錄。Laravel 的授權功能提供了一種簡單且有條理的方法來管理這些種類的授權。
Laravel 提供了兩種主要方法來授權動作:Gate 與 Policy。可以把 Gate 與 Policy 想成是路由與 Controller。Gate 提供了一種簡單、基於閉包的方法來進行授權;而 Policy 就像 Controller 一樣,可以將授權邏輯依照特定的 Model 或資源來進行分組。在本說明文件中,我們會先來探討 Gate,然後再來看看 Policy。
在製作應用程式時,不需要只使用 Gate 或只使用 Policy。大多數應用程式都在某種程度上組合使用 Gate 與 Policy,這樣完全沒問題!Gate 最適合用來處理與 Model 或資源沒關係的操作,如檢視管理員縱覽頁。相較之下,Policy 則應使用於想授權對特定 Model 或 Resource 的操作時
Gate
撰寫 Gate
Gate 是學習基礎 Laravel 授權功能的最好的方法。但是,在製作大型 Laravel 應用程式時,應考慮通過 Policy 來整理各個授權規則。
Gates are simply closures that determine if a user is authorized to perform a given action. Typically, gates are defined within the boot
method of the App\Providers\AppServiceProvider
class using the Gate
facade. Gates always receive a user instance as their first argument and may optionally receive additional arguments such as a relevant Eloquent model.
在此範例中,我們會定義一個用來判斷使用者能否更新給定 App\Models\Post
Model 的 Gate。這個 Gate 會通過比對使用者的 id
與建立該貼文的 user_id
來進行判斷:
1use App\Models\Post;2use App\Models\User;3use Illuminate\Support\Facades\Gate;45/**6 * Bootstrap any application services.7 */8public function boot(): void9{10 Gate::define('update-post', function (User $user, Post $post) {11 return $user->id === $post->user_id;12 });13}
1use App\Models\Post;2use App\Models\User;3use Illuminate\Support\Facades\Gate;45/**6 * Bootstrap any application services.7 */8public function boot(): void9{10 Gate::define('update-post', function (User $user, Post $post) {11 return $user->id === $post->user_id;12 });13}
就像 Controller 一樣,Gate 也可以通過類別回呼陣列來定義:
1use App\Policies\PostPolicy;2use Illuminate\Support\Facades\Gate;34/**5 * Bootstrap any application services.6 */7public function boot(): void8{9 Gate::define('update-post', [PostPolicy::class, 'update']);10}
1use App\Policies\PostPolicy;2use Illuminate\Support\Facades\Gate;34/**5 * Bootstrap any application services.6 */7public function boot(): void8{9 Gate::define('update-post', [PostPolicy::class, 'update']);10}
授權動作
若要通過 Gate 來授權某個動作,可以使用 Gate
Facade 提供的 allows
或 denies
方法。請注意,不需要將目前已登入的使用者傳給這幾個方法。Laravel 會自動處理好將使用者傳給 Gate 閉包。通常,我們會在 Controller 執行需要授權的特定動作前呼叫這些 Gate 授權方法:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;9use Illuminate\Support\Facades\Gate;1011class PostController extends Controller12{13 /**14 * Update the given post.15 */16 public function update(Request $request, Post $post): RedirectResponse17 {18 if (! Gate::allows('update-post', $post)) {19 abort(403);20 }2122 // Update the post...2324 return redirect('/posts');25 }26}
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;9use Illuminate\Support\Facades\Gate;1011class PostController extends Controller12{13 /**14 * Update the given post.15 */16 public function update(Request $request, Post $post): RedirectResponse17 {18 if (! Gate::allows('update-post', $post)) {19 abort(403);20 }2122 // Update the post...2324 return redirect('/posts');25 }26}
如果想判斷除了目前登入使用者以外其他的使用者能否執行特定動作,可以使用 Gate
Facade 上的 forUser
方法:
1if (Gate::forUser($user)->allows('update-post', $post)) {2 // The user can update the post...3}45if (Gate::forUser($user)->denies('update-post', $post)) {6 // The user can't update the post...7}
1if (Gate::forUser($user)->allows('update-post', $post)) {2 // The user can update the post...3}45if (Gate::forUser($user)->denies('update-post', $post)) {6 // The user can't update the post...7}
可以通過 any
或 none
方法來在任何時候授權多個動作:
1if (Gate::any(['update-post', 'delete-post'], $post)) {2 // The user can update or delete the post...3}45if (Gate::none(['update-post', 'delete-post'], $post)) {6 // The user can't update or delete the post...7}
1if (Gate::any(['update-post', 'delete-post'], $post)) {2 // The user can update or delete the post...3}45if (Gate::none(['update-post', 'delete-post'], $post)) {6 // The user can't update or delete the post...7}
Authorizing or Throwing Exceptions
If you would like to attempt to authorize an action and automatically throw an Illuminate\Auth\Access\AuthorizationException
if the user is not allowed to perform the given action, you may use the Gate
facade's authorize
method. Instances of AuthorizationException
are automatically converted to a 403 HTTP response by Laravel:
1Gate::authorize('update-post', $post);23// The action is authorized...
1Gate::authorize('update-post', $post);23// The action is authorized...
提供額外的上下文
用於授權權限的 Gate 方法 (allows
, denies
, check
, any
, none
, authorize
, can
, cannot
) 與授權的 Blade 指示詞 (@can
, @cannot
, @canany
) 都接受一個陣列作為其第二引數。這些陣列的元素會被作為引數傳給 Gate 閉包,並且可在進行權限認證時提供額外的上下文:
1use App\Models\Category;2use App\Models\User;3use Illuminate\Support\Facades\Gate;45Gate::define('create-post', function (User $user, Category $category, bool $pinned) {6 if (! $user->canPublishToGroup($category->group)) {7 return false;8 } elseif ($pinned && ! $user->canPinPosts()) {9 return false;10 }1112 return true;13});1415if (Gate::check('create-post', [$category, $pinned])) {16 // The user can create the post...17}
1use App\Models\Category;2use App\Models\User;3use Illuminate\Support\Facades\Gate;45Gate::define('create-post', function (User $user, Category $category, bool $pinned) {6 if (! $user->canPublishToGroup($category->group)) {7 return false;8 } elseif ($pinned && ! $user->canPinPosts()) {9 return false;10 }1112 return true;13});1415if (Gate::check('create-post', [$category, $pinned])) {16 // The user can create the post...17}
Gate 回應
到目前為止,我們只看過了回傳簡單布林值的 Gate。但,有時候我們可能會想回傳一些更具體的回覆,並在其中包含錯誤訊息。為此,可以在 Gate 內回傳 Illuminate\Auth\Access\Response
:
1use App\Models\User;2use Illuminate\Auth\Access\Response;3use Illuminate\Support\Facades\Gate;45Gate::define('edit-settings', function (User $user) {6 return $user->isAdmin7 ? Response::allow()8 : Response::deny('You must be an administrator.');9});
1use App\Models\User;2use Illuminate\Auth\Access\Response;3use Illuminate\Support\Facades\Gate;45Gate::define('edit-settings', function (User $user) {6 return $user->isAdmin7 ? Response::allow()8 : Response::deny('You must be an administrator.');9});
即使從 Gate 內回傳授權回應,Gate::allows
方法依然會回傳簡單的布林值。不過,可以使用 Gate::inspect
方法來取得 Gate 回傳的完整授權回應:
1$response = Gate::inspect('edit-settings');23if ($response->allowed()) {4 // The action is authorized...5} else {6 echo $response->message();7}
1$response = Gate::inspect('edit-settings');23if ($response->allowed()) {4 // The action is authorized...5} else {6 echo $response->message();7}
在使用 Gate::authorize
方法時,若動作未被授權,會回傳 AuthorizationException
。這時,授權回應所提供的錯誤訊息會進一步被傳給 HTTP 回應:
1Gate::authorize('edit-settings');23// The action is authorized...
1Gate::authorize('edit-settings');23// The action is authorized...
自定 HTTP Response 狀態
當 Gate 拒絕某個動作時,會回傳 403
HTTP Response。不過,在某些情況下,若能回傳其他的 HTTP 狀態碼更好。我們可以使用 Illuminate\Auth\Access\Response
類別的 denyWithStatus
靜態建構函式來自訂在授權檢查失敗的時候要回傳的 HTTP 狀態碼:
1use App\Models\User;2use Illuminate\Auth\Access\Response;3use Illuminate\Support\Facades\Gate;45Gate::define('edit-settings', function (User $user) {6 return $user->isAdmin7 ? Response::allow()8 : Response::denyWithStatus(404);9});
1use App\Models\User;2use Illuminate\Auth\Access\Response;3use Illuminate\Support\Facades\Gate;45Gate::define('edit-settings', function (User $user) {6 return $user->isAdmin7 ? Response::allow()8 : Response::denyWithStatus(404);9});
而因為使用 404
Response 來隱藏資源是在 Web App 中常見的手段,因此為了方便, Laravel 也提供了 denyAsNotFound
方法:
1use App\Models\User;2use Illuminate\Auth\Access\Response;3use Illuminate\Support\Facades\Gate;45Gate::define('edit-settings', function (User $user) {6 return $user->isAdmin7 ? Response::allow()8 : Response::denyAsNotFound();9});
1use App\Models\User;2use Illuminate\Auth\Access\Response;3use Illuminate\Support\Facades\Gate;45Gate::define('edit-settings', function (User $user) {6 return $user->isAdmin7 ? Response::allow()8 : Response::denyAsNotFound();9});
攔截 Gate 檢查
有的時候,我們可能會想授權特定使用者所有的權限。可以使用 before
方法來定義會在所有權限檢查前執行的閉包:
1use App\Models\User;2use Illuminate\Support\Facades\Gate;34Gate::before(function (User $user, string $ability) {5 if ($user->isAdministrator()) {6 return true;7 }8});
1use App\Models\User;2use Illuminate\Support\Facades\Gate;34Gate::before(function (User $user, string $ability) {5 if ($user->isAdministrator()) {6 return true;7 }8});
若 before
閉包回傳了一個非 null 的結果,則該結果值會被當作權限檢查的結果。
可以使用 after
方法來定義一個會在所有其他權限檢查後執行的閉包:
1use App\Models\User;23Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) {4 if ($user->isAdministrator()) {5 return true;6 }7});
1use App\Models\User;23Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) {4 if ($user->isAdministrator()) {5 return true;6 }7});
Values returned by after
closures will not override the result of the authorization check unless the gate or policy returned null
.
內嵌授權
Occasionally, you may wish to determine if the currently authenticated user is authorized to perform a given action without writing a dedicated gate that corresponds to the action. Laravel allows you to perform these types of "inline" authorization checks via the Gate::allowIf
and Gate::denyIf
methods. Inline authorization does not execute any defined "before" or "after" authorization hooks:
1use App\Models\User;2use Illuminate\Support\Facades\Gate;34Gate::allowIf(fn (User $user) => $user->isAdministrator());56Gate::denyIf(fn (User $user) => $user->banned());
1use App\Models\User;2use Illuminate\Support\Facades\Gate;34Gate::allowIf(fn (User $user) => $user->isAdministrator());56Gate::denyIf(fn (User $user) => $user->banned());
若該動作未授權、或是使用者未登入,則 Laravel 會自動擲回 Illuminate\Auth\Access\AuthorizationException
例外。AuthorizationException
實體會自動由 Laravel 的例外處理常式轉換為 403 HTTP 回應。
建立 Policy
產生 Policy
Policy 是用來依照特定 Model 或資源阻止授權邏輯的類別。舉例來說,若你的專案是個部落格,則可能會有 App\Models\Post
Model 以及對應的 App\Policies\PostPolicy
來授權使用者進行建立或更新貼文之類的動作。
可以通過 make:policy
Artisan 指令來產生 Policy。產生的 Policy 會被放在 app/Policies
目錄內。若專案中沒有該目錄中,則 Laravel 會自動建立:
1php artisan make:policy PostPolicy
1php artisan make:policy PostPolicy
make:policy
指令會產生一個空的 Policy 類別。若想產生一個與檢視 (View)、建立 (Create)、更新 (Update)、刪除 (Delete) 資源有關的範例 方法的 Policy 類別,可以在執行指令時提供 --model
選項:
1php artisan make:policy PostPolicy --model=Post
1php artisan make:policy PostPolicy --model=Post
註冊 Policy
Policy Discovery
By default, Laravel automatically discover policies as long as the model and policy follow standard Laravel naming conventions. Specifically, the policies must be in a Policies
directory at or above the directory that contains your models. So, for example, the models may be placed in the app/Models
directory while the policies may be placed in the app/Policies
directory. In this situation, Laravel will check for policies in app/Models/Policies
then app/Policies
. In addition, the policy name must match the model name and have a Policy
suffix. So, a User
model would correspond to a UserPolicy
policy class.
If you would like to define your own policy discovery logic, you may register a custom policy discovery callback using the Gate::guessPolicyNamesUsing
method. Typically, this method should be called from the boot
method of your application's AppServiceProvider
:
1use Illuminate\Support\Facades\Gate;23Gate::guessPolicyNamesUsing(function (string $modelClass) {4 // Return the name of the policy class for the given model...5});
1use Illuminate\Support\Facades\Gate;23Gate::guessPolicyNamesUsing(function (string $modelClass) {4 // Return the name of the policy class for the given model...5});
Manually Registering Policies
Using the Gate
facade, you may manually register policies and their corresponding models within the boot
method of your application's AppServiceProvider
:
1use App\Models\Order;2use App\Policies\OrderPolicy;3use Illuminate\Support\Facades\Gate;45/**6 * Bootstrap any application services.7 */8public function boot(): void9{10 Gate::policy(Order::class, OrderPolicy::class);11}
1use App\Models\Order;2use App\Policies\OrderPolicy;3use Illuminate\Support\Facades\Gate;45/**6 * Bootstrap any application services.7 */8public function boot(): void9{10 Gate::policy(Order::class, OrderPolicy::class);11}
撰寫 Policy
Policy 方法
註冊好 Policy 類別後,可以為每個授權的動作加上方法。舉例來說,我們來在 PostPolicy
中定義一個 update
方法,用來判斷給定的 App\Models\User
可否更新給定的 App\Models\Post
實體。
update
方法會在其引數內收到 User
與 Post
實體,並且應回傳 true
或 false
來判斷該使用者是否有權限更新給定的 Post
。因此,在這個例子中,我們會認證使用者的 id
是否與貼文的 user_id
相符:
1<?php23namespace App\Policies;45use App\Models\Post;6use App\Models\User;78class PostPolicy9{10 /**11 * Determine if the given post can be updated by the user.12 */13 public function update(User $user, Post $post): bool14 {15 return $user->id === $post->user_id;16 }17}
1<?php23namespace App\Policies;45use App\Models\Post;6use App\Models\User;78class PostPolicy9{10 /**11 * Determine if the given post can be updated by the user.12 */13 public function update(User $user, Post $post): bool14 {15 return $user->id === $post->user_id;16 }17}
我們可以繼續在 Policy 內為各種所需的權限檢查定義更多額外方法。舉例來說,我們可以定義 view
或 delete
方法來對各種與 Post
有關的動作進行權限檢查。但不要忘了,你可以隨意為 Policy 的方法命名。
通過 Artisan 主控台產生 Policy 時若有使用 --model
選項,則 Policy 就已經包含了 viewAny
, view
, create
, update
, delete
, restore
與 forceDelete
動作的方法。
所有的 Policy 都經由 Laravel 的 Service Container 進行解析,這樣一來,可以在 Policy 的建構函式 (Constructor) 內對任何所需的相依項進行型別提示,這些相依項會被自動插入到 Class 內。
Policy 回應
到目前為止,我們只看過了回傳簡單布林值的 Policy 方法。但,有時候我們可能會想回傳一些更具體的回覆,並在其中包含錯誤訊息。為此,可以在 Policy 方法內回傳 Illuminate\Auth\Access\Response
:
1use App\Models\Post;2use App\Models\User;3use Illuminate\Auth\Access\Response;45/**6 * Determine if the given post can be updated by the user.7 */8public function update(User $user, Post $post): Response9{10 return $user->id === $post->user_id11 ? Response::allow()12 : Response::deny('You do not own this post.');13}
1use App\Models\Post;2use App\Models\User;3use Illuminate\Auth\Access\Response;45/**6 * Determine if the given post can be updated by the user.7 */8public function update(User $user, Post $post): Response9{10 return $user->id === $post->user_id11 ? Response::allow()12 : Response::deny('You do not own this post.');13}
當從 Policy 內回傳授權回應時,Gate::allows
方法依然會回傳簡單的布林值。不過,可以使用 Gate::inspect
方法來取得 Gate 回傳的完整授權回應:
1use Illuminate\Support\Facades\Gate;23$response = Gate::inspect('update', $post);45if ($response->allowed()) {6 // The action is authorized...7} else {8 echo $response->message();9}
1use Illuminate\Support\Facades\Gate;23$response = Gate::inspect('update', $post);45if ($response->allowed()) {6 // The action is authorized...7} else {8 echo $response->message();9}
在使用 Gate::authorize
方法時,若動作未被授權,會回傳 AuthorizationException
。這時,授權回應所提供的錯誤訊息會進一步被傳給 HTTP 回應:
1Gate::authorize('update', $post);23// The action is authorized...
1Gate::authorize('update', $post);23// The action is authorized...
Customizing the HTTP Response Status
當 Policy 方法拒絕某個動作時,會回傳 403
HTTP Response。不過,在某些情況下,若能回傳其他的 HTTP 狀態碼更好。我們可以使用 Illuminate\Auth\Access\Response
類別的 denyWithStatus
靜態建構函式來自訂在授權檢查失敗的時候要回傳的 HTTP 狀態碼:
1use App\Models\Post;2use App\Models\User;3use Illuminate\Auth\Access\Response;45/**6 * Determine if the given post can be updated by the user.7 */8public function update(User $user, Post $post): Response9{10 return $user->id === $post->user_id11 ? Response::allow()12 : Response::denyWithStatus(404);13}
1use App\Models\Post;2use App\Models\User;3use Illuminate\Auth\Access\Response;45/**6 * Determine if the given post can be updated by the user.7 */8public function update(User $user, Post $post): Response9{10 return $user->id === $post->user_id11 ? Response::allow()12 : Response::denyWithStatus(404);13}
而因為使用 404
Response 來隱藏資源是在 Web App 中常見的手段,因此為了方便, Laravel 也提供了 denyAsNotFound
方法:
1use App\Models\Post;2use App\Models\User;3use Illuminate\Auth\Access\Response;45/**6 * Determine if the given post can be updated by the user.7 */8public function update(User $user, Post $post): Response9{10 return $user->id === $post->user_id11 ? Response::allow()12 : Response::denyAsNotFound();13}
1use App\Models\Post;2use App\Models\User;3use Illuminate\Auth\Access\Response;45/**6 * Determine if the given post can be updated by the user.7 */8public function update(User $user, Post $post): Response9{10 return $user->id === $post->user_id11 ? Response::allow()12 : Response::denyAsNotFound();13}
沒有 Model 的方法
有些 Policy 方法只會收到目前登入使用者的實體。最常見的情況就是在授權 create
動作時。舉例來說,若正在建立部落格,則可能會想判斷某個使用者是否有權限建立任何貼文。在這種情況想,Policy 方法應該只會收到使用者實體:
1/**2 * Determine if the given user can create posts.3 */4public function create(User $user): bool5{6 return $user->role == 'writer';7}
1/**2 * Determine if the given user can create posts.3 */4public function create(User $user): bool5{6 return $user->role == 'writer';7}
訪客使用者
預設情況下,當連入 HTTP 請求並不是由已登入使用者發起的時候,所有的 Gate 與 Policy 都會回傳 false
。不過,我們可以通過在使用者的引數定義上定義「可選」的型別提示,或是提供一個 null
預設值,來讓這些權限檢查可以進到 Gate 與 Policy 中:
1<?php23namespace App\Policies;45use App\Models\Post;6use App\Models\User;78class PostPolicy9{10 /**11 * Determine if the given post can be updated by the user.12 */13 public function update(?User $user, Post $post): bool14 {15 return $user?->id === $post->user_id;16 }17}
1<?php23namespace App\Policies;45use App\Models\Post;6use App\Models\User;78class PostPolicy9{10 /**11 * Determine if the given post can be updated by the user.12 */13 public function update(?User $user, Post $post): bool14 {15 return $user?->id === $post->user_id;16 }17}
Policy 篩選器
我們可能會想讓特定使用者擁有某個 Policy 中擁有的所有權限。為此,可以在 Policy 內定義一個 before
方法。before
方法會在 Policy 內任何其他方法之前被執行,如此一來我們便有機會可以在預定的 Policy 方法被實際執行前對該行為進行授權。這個功能最常見的使用情況就是用來授權網站管理員來進行所有動作:
1use App\Models\User;23/**4 * Perform pre-authorization checks.5 */6public function before(User $user, string $ability): bool|null7{8 if ($user->isAdministrator()) {9 return true;10 }1112 return null;13}
1use App\Models\User;23/**4 * Perform pre-authorization checks.5 */6public function before(User $user, string $ability): bool|null7{8 if ($user->isAdministrator()) {9 return true;10 }1112 return null;13}
若拒絕特定類型的使用者的所有授權,可以在 before
方法內回傳 false
。若回傳 null
,則權限檢查會繼續傳到 Policy 方法內。
若 Policy 類別內不含要檢查權限名稱的方法,則 before
方法將不會被呼叫。
通過 Policy 來授權動作
Via the User Model
Laravel 專案內建的 App\Models\User
Model 中包含了兩個實用的方法,可以用來進行權限檢查:can
與 cannot
。can
與 cannot
方法接收用要進行權限檢查的動作名稱,以及相關的 Model。舉例來說,讓我們來判斷某個使用者是否有權限更新給定的 App\Models\Post
Model。一般來說,這個檢查會在 Controller 的方法內進行:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;910class PostController extends Controller11{12 /**13 * Update the given post.14 */15 public function update(Request $request, Post $post): RedirectResponse16 {17 if ($request->user()->cannot('update', $post)) {18 abort(403);19 }2021 // Update the post...2223 return redirect('/posts');24 }25}
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;910class PostController extends Controller11{12 /**13 * Update the given post.14 */15 public function update(Request $request, Post $post): RedirectResponse16 {17 if ($request->user()->cannot('update', $post)) {18 abort(403);19 }2021 // Update the post...2223 return redirect('/posts');24 }25}
若已為給定的 Model 註冊好 Policy,則 can
方法會自動呼叫適當的 Policy,並回傳布林結果值。若沒有為該 Model 註冊好的 Policy,則 can
方法會呼叫符合給定動作名稱的閉包 Gate。
不需要 Model 的動作
請記得,某些對應到 Policy 方法的動作,如 create
,並不要求 Model 實體。這種情況下,可以將類別名稱傳給 can
方法。類別名稱會用來判斷對動作進行權限檢查時要使用哪個 Policy:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;910class PostController extends Controller11{12 /**13 * Create a post.14 */15 public function store(Request $request): RedirectResponse16 {17 if ($request->user()->cannot('create', Post::class)) {18 abort(403);19 }2021 // Create the post...2223 return redirect('/posts');24 }25}
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;910class PostController extends Controller11{12 /**13 * Create a post.14 */15 public function store(Request $request): RedirectResponse16 {17 if ($request->user()->cannot('create', Post::class)) {18 abort(403);19 }2021 // Create the post...2223 return redirect('/posts');24 }25}
Via the Gate
Facade
In addition to helpful methods provided to the App\Models\User
model, you can always authorize actions via the Gate
facade's authorize
method.
與 can
方法類似,這個方法接收要進行權限檢查的動作名稱、以及相關的 Model。若該動作未被授權,則 authorize
方法會擲回 Illuminate\Auth\Access\AuthroizationException
例外,Laravel 的例外處理常式會自動將該例外轉成有 403 狀態碼的 HTTP 回應:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;9use Illuminate\Support\Facades\Gate;1011class PostController extends Controller12{13 /**14 * Update the given blog post.15 *16 * @throws \Illuminate\Auth\Access\AuthorizationException17 */18 public function update(Request $request, Post $post): RedirectResponse19 {20 Gate::authorize('update', $post);2122 // The current user can update the blog post...2324 return redirect('/posts');25 }26}
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Models\Post;7use Illuminate\Http\RedirectResponse;8use Illuminate\Http\Request;9use Illuminate\Support\Facades\Gate;1011class PostController extends Controller12{13 /**14 * Update the given blog post.15 *16 * @throws \Illuminate\Auth\Access\AuthorizationException17 */18 public function update(Request $request, Post $post): RedirectResponse19 {20 Gate::authorize('update', $post);2122 // The current user can update the blog post...2324 return redirect('/posts');25 }26}
不需要 Model 的動作
與前面討論過的一樣,某些 Policy 方法,如 create
,並不要求 Model 實體。這種情況下,應將類別名稱傳給 authorize
方法。類別名稱會用來判斷對動作進行權限檢查時要使用哪個 Policy:
1use App\Models\Post;2use Illuminate\Http\RedirectResponse;3use Illuminate\Http\Request;4use Illuminate\Support\Facades\Gate;56/**7 * Create a new blog post.8 *9 * @throws \Illuminate\Auth\Access\AuthorizationException10 */11public function create(Request $request): RedirectResponse12{13 Gate::authorize('create', Post::class);1415 // The current user can create blog posts...1617 return redirect('/posts');18}
1use App\Models\Post;2use Illuminate\Http\RedirectResponse;3use Illuminate\Http\Request;4use Illuminate\Support\Facades\Gate;56/**7 * Create a new blog post.8 *9 * @throws \Illuminate\Auth\Access\AuthorizationException10 */11public function create(Request $request): RedirectResponse12{13 Gate::authorize('create', Post::class);1415 // The current user can create blog posts...1617 return redirect('/posts');18}
通過 Middleware
Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the Illuminate\Auth\Middleware\Authorize
middleware may be attached to a route using the can
middleware alias, which is automatically registered by Laravel. Let's explore an example of using the can
middleware to authorize that a user can update a post:
1use App\Models\Post;23Route::put('/post/{post}', function (Post $post) {4 // The current user may update the post...5})->middleware('can:update,post');
1use App\Models\Post;23Route::put('/post/{post}', function (Post $post) {4 // The current user may update the post...5})->middleware('can:update,post');
在此例子中,我們將兩個引數傳給了 can
Middleware。第一個引數是我們想進行權限檢查的動作名稱,而第二個引數是我們想傳給 Policy 方法的路由參數。在這個例子中,由於我們使用了隱式 Model 繫結,所以會將 App\Models\Post
Model 傳給 Policy 方法。若使用者沒有權限執行給定的動作,則這個 Middleware 會回傳狀態碼 403 的 HTTP 回應。
為了方便起見,也可以使用 can
方法來將 can
Middleware 附加到路由上:
1use App\Models\Post;23Route::put('/post/{post}', function (Post $post) {4 // The current user may update the post...5})->can('update', 'post');
1use App\Models\Post;23Route::put('/post/{post}', function (Post $post) {4 // The current user may update the post...5})->can('update', 'post');
不需要 Model 的動作
再強調一次,某些 Policy 方法,如 create
,並不要求 Model 實體。這種情況下,可以將類別名稱傳給 Middleware。這個類別名稱會用來判斷對動作進行權限檢查時要使用哪個 Policy:
1Route::post('/post', function () {2 // The current user may create posts...3})->middleware('can:create,App\Models\Post');
1Route::post('/post', function () {2 // The current user may create posts...3})->middleware('can:create,App\Models\Post');
在字串形式的 Middleware 定義中指定完整的類別名稱可能會有點麻煩。因此,我們也可以使用 can
方法來將 can
Middleware 附加到路由上:
1use App\Models\Post;23Route::post('/post', function () {4 // The current user may create posts...5})->can('create', Post::class);
1use App\Models\Post;23Route::post('/post', function () {4 // The current user may create posts...5})->can('create', Post::class);
通過 Blade 樣板
在撰寫 Blade 樣板時,我們可能會在使用者有權限執行給定動作時顯示某一部分的頁面。舉例來說,我們可能想在使用者真的可以更新貼文時才顯示更新表單。這時,可以使用 @can
與 @cannot
指示詞:
1@can('update', $post)2 <!-- The current user can update the post... -->3@elsecan('create', App\Models\Post::class)4 <!-- The current user can create new posts... -->5@else6 <!-- ... -->7@endcan89@cannot('update', $post)10 <!-- The current user cannot update the post... -->11@elsecannot('create', App\Models\Post::class)12 <!-- The current user cannot create new posts... -->13@endcannot
1@can('update', $post)2 <!-- The current user can update the post... -->3@elsecan('create', App\Models\Post::class)4 <!-- The current user can create new posts... -->5@else6 <!-- ... -->7@endcan89@cannot('update', $post)10 <!-- The current user cannot update the post... -->11@elsecannot('create', App\Models\Post::class)12 <!-- The current user cannot create new posts... -->13@endcannot
這些指示詞是撰寫 @if
與 @unless
陳述式時的方便捷徑。上方的 @can
與 @cannot
陳述式與下列陳述式相同:
1@if (Auth::user()->can('update', $post))2 <!-- The current user can update the post... -->3@endif45@unless (Auth::user()->can('update', $post))6 <!-- The current user cannot update the post... -->7@endunless
1@if (Auth::user()->can('update', $post))2 <!-- The current user can update the post... -->3@endif45@unless (Auth::user()->can('update', $post))6 <!-- The current user cannot update the post... -->7@endunless
可以在包含一系列動作的陣列中判斷某個使用者是否有權限執行其中的任意動作。為此,請使用 @canany
指示詞:
1@canany(['update', 'view', 'delete'], $post)2 <!-- The current user can update, view, or delete the post... -->3@elsecanany(['create'], \App\Models\Post::class)4 <!-- The current user can create a post... -->5@endcanany
1@canany(['update', 'view', 'delete'], $post)2 <!-- The current user can update, view, or delete the post... -->3@elsecanany(['create'], \App\Models\Post::class)4 <!-- The current user can create a post... -->5@endcanany
不需要 Model 的動作
與其他大多數的授權方法一樣,當某個動作不需要 Model 實體時,可以將類別名稱傳給 @can
與 @cannot
指示詞:
1@can('create', App\Models\Post::class)2 <!-- The current user can create posts... -->3@endcan45@cannot('create', App\Models\Post::class)6 <!-- The current user can't create posts... -->7@endcannot
1@can('create', App\Models\Post::class)2 <!-- The current user can create posts... -->3@endcan45@cannot('create', App\Models\Post::class)6 <!-- The current user can't create posts... -->7@endcannot
提供額外的上下文
當使用 Policy 對動作進行權限檢查時,可以將陣列作為第二引數傳給各種權限檢查函式與輔助函式。陣列中的第一個元素是用來判斷要叫用哪個 Policy 的,而剩下的元素則會作為參數傳給 Policy 方法,可用來在做權限檢查時提供額外的上下文。舉例來說,假設有下列 PostPolicy
方法定義,其中包含了一個額外的 $category
參數:
1/**2 * Determine if the given post can be updated by the user.3 */4public function update(User $user, Post $post, int $category): bool5{6 return $user->id === $post->user_id &&7 $user->canUpdateCategory($category);8}
1/**2 * Determine if the given post can be updated by the user.3 */4public function update(User $user, Post $post, int $category): bool5{6 return $user->id === $post->user_id &&7 $user->canUpdateCategory($category);8}
在嘗試判斷登入使用者能否更新給定貼文時,我們可以像這樣叫用該 Policy 方法:
1/**2 * Update the given blog post.3 *4 * @throws \Illuminate\Auth\Access\AuthorizationException5 */6public function update(Request $request, Post $post): RedirectResponse7{8 Gate::authorize('update', [$post, $request->category]);910 // The current user can update the blog post...1112 return redirect('/posts');13}
1/**2 * Update the given blog post.3 *4 * @throws \Illuminate\Auth\Access\AuthorizationException5 */6public function update(Request $request, Post $post): RedirectResponse7{8 Gate::authorize('update', [$post, $request->category]);910 // The current user can update the blog post...1112 return redirect('/posts');13}
Authorization & Inertia
Although authorization must always be handled on the server, it can often be convenient to provide your frontend application with authorization data in order to properly render your application's UI. Laravel does not define a required convention for exposing authorization information to an Inertia powered frontend.
However, if you are using one of Laravel's Inertia-based starter kits, your application already contains a HandleInertiaRequests
middleware. Within this middleware's share
method, you may return shared data that will be provided to all Inertia pages in your application. This shared data can serve as a convenient location to define authorization information for the user:
1<?php23namespace App\Http\Middleware;45use App\Models\Post;6use Illuminate\Http\Request;7use Inertia\Middleware;89class HandleInertiaRequests extends Middleware10{11 // ...1213 /**14 * Define the props that are shared by default.15 *16 * @return array<string, mixed>17 */18 public function share(Request $request)19 {20 return [21 ...parent::share($request),22 'auth' => [23 'user' => $request->user(),24 'permissions' => [25 'post' => [26 'create' => $request->user()->can('create', Post::class),27 ],28 ],29 ],30 ];31 }32}
1<?php23namespace App\Http\Middleware;45use App\Models\Post;6use Illuminate\Http\Request;7use Inertia\Middleware;89class HandleInertiaRequests extends Middleware10{11 // ...1213 /**14 * Define the props that are shared by default.15 *16 * @return array<string, mixed>17 */18 public function share(Request $request)19 {20 return [21 ...parent::share($request),22 'auth' => [23 'user' => $request->user(),24 'permissions' => [25 'post' => [26 'create' => $request->user()->can('create', Post::class),27 ],28 ],29 ],30 ];31 }32}