使用者授權


使用者授權

  • ##簡介
  • Gates
    • 寫Gates
    • #授權動作
    • ##Gate攔截檢查
    • 建立策略
    • #產生策略
    • 註冊策略
    • 寫策略
  • #策略方法
    • 不包含模型方法
    • 訪客- 使用者
    • 策略過濾器
    • ##使用策略去授權動作
    透過使用者模型

透過中間件

透過控制器輔助函數

透過Blade 範本

############## #簡介######除了提供開箱即用的使用者認證服務外,Laravel 還提供了一種簡單的方法來處理使用者的授權動作。與使用者認證一樣,Laravel 的授權方法很簡單,授權操作有兩種主要方式:gates 和策略。 ######可以把 gates 和政策比喻為路由和控制器。 Gates 提供了一種簡單的基於閉包的授權方法,而策略和控制器類似,圍繞特定模型或資源對其邏輯進行分組來實現授權認證。我們先探索 gates,然後再研究策略。 ######在建構一個應用程式的時候,不用在專門使用 gates 或只使用策略之間進行選擇。大部分的應用程式很可能同時包含 gates 和策略, 並且能夠很好地進行工作。 Gates 大部分應用在模型和資源沒有關係的地方,例如查看管理員的面板。與之相反,策略應該在特定的模型或資源中使用。 ########################Gates########################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.imageposts.photo:

Gate::resource('posts', 'PostPolicy', [ 
   'image' => 'updateImage',    
   'photo' => 'updatePhoto',
 ]);

授權動作

使用gates 來授權動作的時候, 你應該使用allowsdenies 方法。注意,不需要將目前已認證使用者傳遞給這些方法。 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 方法註冊自訂回呼。通常,應該從應用程式的AuthServiceProviderboot 方法呼叫此方法:

use Illuminate\Support\Facades\Gate;
Gate::guessPolicyNamesUsing(function ($modelClass) {
    // return policy class name...
 });

{note} 在AuthServiceProvider 中顯式映射的任何策略都將優先於自動發現策略。

寫策略

#########################################################

策略方法

一旦授權策略被註冊,你就可以為授權過後的每個動作添加方法。例如,我們在 PostPolicy 中定義一個 update 方法,它會判斷指定的 User 是否可以更新指定的 Post 實例。

update 方法接收UserPost 實例作為參數,並且應該傳回truefalse 來表示使用者是否被授權更新指定的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;   
     }
 }

你可以繼續為這個授權策略定義額外的方法。例如,你可以定義viewdelete 方法來授權Post 的多種行為,也可以為自訂的策略方法取一個你自己喜歡的名字。

{tip} 如果在Artisan 控制台產生策略時,使用--model 選項,它會包含進去view#,createupdatedelete 動作方法。

不包含模型方法

一些策略方法只接收目前認證通過的使用者作為參數,而不用傳入與授權相關的模型實例。最常見的應用程式場景就是授權 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 模型包含兩個有用的方法來授權動作: cancant 。這個 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
本篇首發在LearnKu.com 網站上。