Laravel 文件閱讀:授權

zhangbao發表於2017-09-29

翻譯、衍生自:https://learnku.com/docs/laravel/5.5/authorization

簡介

除了開箱提供的認證服務,在 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');

預設,會定義 viewcreateupdatedelete 能力。如果要覆蓋或新增,使用 resource 方法的第三個陣列引數。陣列的 Key 是能力名,Value 是方法名。下面的例子裡,我們定義了兩個 Gates 能力:posts.imageposts.photo

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

授權操作

使用 Gates 授權操作,是用到 allowsdenies 方法。需要注意的是,你不需要傳遞當前的認證使用者例項給這兩個方法,因為 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 的服務容器解析的,因此你可以在策略類的建構函式裡新增任何你需要依賴注入的例項。

註冊策略

策略類生成完畢後需要註冊它。AuthServiceProviderpolicies 陣列屬性就是幹這個的,在這裡為你的 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 選項生成策略類的時候,會自動生成針對增刪改查操作的 createdeleteupdateview 策略方法。

無 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 中包含兩個有用的授權方法:cancantcan 方法接收兩個引數,第一個引數是操作名,第二個就是相關 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 模板中,提供了兩個用來判斷使用者是否具有操作許可權的指令:@cancannot。它們在判斷頁面是否該顯示一些內容上比較有用,使用方式如下:

@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 協議》,轉載必須註明作者和本文連結

相關文章