鍍金池/ 教程/ PHP/ 用戶授權(quán)
門(mén)面
Laravel Homestead
安裝及配置
測(cè)試
HTTP 中間件
加密
升級(jí)指南
幫助函數(shù)
應(yīng)用目錄結(jié)構(gòu)
集合
新手入門(mén)指南-簡(jiǎn)單任務(wù)管理系統(tǒng)
任務(wù)調(diào)度
查詢構(gòu)建器
視圖
驗(yàn)證
Laravel Cashier(訂購(gòu)&支付&發(fā)票)
本地化
隊(duì)列
調(diào)整器
分頁(yè)
文件系統(tǒng)/云存儲(chǔ)
貢獻(xiàn)代碼
哈希
HTTP 控制器
緩存
遷移
HTTP 請(qǐng)求
Laravel Elixir
發(fā)行版本說(shuō)明
Envoy 任務(wù)運(yùn)行器(SSH任務(wù))
序列化
Session
起步
帶用戶功能的任務(wù)管理系統(tǒng)
起步
用戶授權(quán)
郵件
事件
填充數(shù)據(jù)
HTTP 路由
服務(wù)提供者
Blade 模板引擎
包開(kāi)發(fā)
用戶認(rèn)證
Artisan 控制臺(tái)
HTTP 響應(yīng)
集合
服務(wù)容器
關(guān)聯(lián)關(guān)系
一次請(qǐng)求的生命周期
契約
Redis
錯(cuò)誤&日志

用戶授權(quán)

1、簡(jiǎn)介

除了提供“開(kāi)箱即用”的認(rèn)證服務(wù)之外,Laravel 還提供了一個(gè)簡(jiǎn)單的方式來(lái)管理授權(quán)邏輯以便控制對(duì)資源的訪問(wèn)權(quán)限。在 Laravel 中,有很多種方法和幫助函數(shù)來(lái)協(xié)助你管理授權(quán)邏輯,本文檔將會(huì)一一覆蓋這些方法。 注意:授權(quán)在 Laravel 5.1.11 版本中引入,在將該特性集成到應(yīng)用之前請(qǐng)參考升級(jí)指南。

2、定義權(quán)限(Abilities)

判斷用戶是否有權(quán)限執(zhí)行給定動(dòng)作的最簡(jiǎn)單方式就是使用 Illuminate\Auth\Access\Gate類(lèi)來(lái)定義一個(gè)“權(quán)限”。我們?cè)?AuthServiceProvider 中定義所有權(quán)限,例如,我們來(lái)定義一個(gè)接收當(dāng)前 UserPost模型的 update-post 權(quán)限,在該權(quán)限中,我們判斷用戶 id 是否和文章的 user_id 匹配:

<?php

namespace App\Providers;

use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider{
    /**
     * 注冊(cè)應(yīng)用所有的認(rèn)證/授權(quán)服務(wù).
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        parent::registerPolicies($gate);

        $gate->define('update-post', function ($user, $post) {
            return $user->id === $post->user_id;
        });
    }
}

注意我們并沒(méi)有檢查給定$user 是否為 NULL,當(dāng)用戶未經(jīng)過(guò)登錄認(rèn)證或者用戶沒(méi)有通過(guò) forUser 方法指定,Gate 會(huì)自動(dòng)為所有權(quán)限返回 false。

基于類(lèi)的權(quán)限 除了注冊(cè)授權(quán)回調(diào)閉包之外,還可以通過(guò)傳遞包含權(quán)限類(lèi)名和類(lèi)方法的方式來(lái)注冊(cè)權(quán)限方法,當(dāng)需要的時(shí)候,該類(lèi)會(huì)通過(guò)服務(wù)容器進(jìn)行解析:

$gate->define('update-post', 'PostPolicy@update');

3、檢查權(quán)限(Abilities)

3.1 通過(guò) Gate 門(mén)面

權(quán)限定義好之后,可以使用多種方式來(lái)“檢查”。首先,可以使用 Gate 門(mén)面的 check, allows, 或者 denies 方法。所有這些方法都接收權(quán)限名和傳遞給該權(quán)限回調(diào)的參數(shù)作為參數(shù)。你不需要傳遞當(dāng)前用戶到這些方法,因?yàn)?Gate 會(huì)自動(dòng)附加當(dāng)前用戶到傳遞給回調(diào)的參數(shù),因此,當(dāng)檢查我們之前定義的 update-post 權(quán)限時(shí),我們只需要傳遞一個(gè) Post 實(shí)例到 denies 方法:

<?php

namespace App\Http\Controllers;

use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;

class PostController extends Controller{
    /**
     * 更新給定文章
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $post = Post::findOrFail($id);

        if (Gate::denies('update-post', $post)) {
            abort(403);
        }

        // 更新文章...
    }
}

當(dāng)然,allows 方法和 denies 方法是相對(duì)的,如果動(dòng)作被授權(quán)會(huì)返回 true ,check 方法是 allows 方法的別名。

為指定用戶檢查權(quán)限 如果你想要使用 Gate 門(mén)面判斷非當(dāng)前用戶是否有權(quán)限,可以使用 forUser 方法:

if (Gate::forUser($user)->allows('update-post', $post)) {
    //
}

傳遞多個(gè)參數(shù) 當(dāng)然,權(quán)限回調(diào)還可以接收多個(gè)參數(shù):

Gate::define('delete-comment', function ($user, $post, $comment) {
    //
});

如果權(quán)限需要多個(gè)參數(shù),簡(jiǎn)單傳遞參數(shù)數(shù)組到 Gate 方法:

if (Gate::allows('delete-comment', [$post, $comment])) {
    //
}

3.2 通過(guò) User 模型

還可以通過(guò) User 模型實(shí)例來(lái)檢查權(quán)限。默認(rèn)情況下,Laravel 的 App\User 模型使用一個(gè) Authorizabletrait 來(lái)提供兩種方法:can 和 cannot。這兩個(gè)方法的功能和 Gate 門(mén)面上的 allowsdenies 方法類(lèi)似。因此,使用我們前面的例子,可以修改代碼如下:

<?php

namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostController extends Controller{
    /**
     * 更新給定文章
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        $post = Post::findOrFail($id);

        if ($request->user()->cannot('update-post', $post)) {
            abort(403);
        }

        // 更新文章...
    }
}

當(dāng)然,can 方法和 cannot 方法相反:

if ($request->user()->can('update-post', $post)) {
    // 更新文章...
}

3.3 在Blade模板引擎中檢查

為了方便,Laravel 提供了 Blade 指令@can 來(lái)快速檢查當(dāng)前用戶是否有指定權(quán)限。例如:

<a href="/post/{{ $post->id }}">View Post</a>

@can('update-post', $post)
    <a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan

你還可以將 @can 指令和@else 指令聯(lián)合起來(lái)使用:

@can('update-post', $post)
    <!-- The Current User Can Update The Post -->
@else
    <!-- The Current User Can't Update The Post -->
@endcan

3.4 在表單請(qǐng)求中檢查

你還可以選擇在表單請(qǐng)求authorize 方法中使用 Gate 定義的權(quán)限。例如:

/**
 * 判斷請(qǐng)求用戶是否經(jīng)過(guò)授權(quán)
 *
 * @return bool
 */
public function authorize(){
    $postId = $this->route('post');
    return Gate::allows('update', Post::findOrFail($postId));
}

4、策略類(lèi)(Policies)

4.1 創(chuàng)建策略類(lèi)

由于在 AuthServiceProvider 中定義所有的授權(quán)邏輯將會(huì)變得越來(lái)越臃腫笨重,尤其是在大型應(yīng)用中,所以 Laravel 允許你將授權(quán)邏輯分割到多個(gè)“策略”類(lèi)中,策略類(lèi)是原生的 PHP 類(lèi),基于授權(quán)資源對(duì)授權(quán)邏輯進(jìn)行分組。

首先,讓我們生成一個(gè)策略類(lèi)來(lái)管理對(duì) Post 模型的授權(quán),你可以使用 Artisan 命令 make:policy 來(lái)生成該策略類(lèi)。生成的策略類(lèi)位于 app/Policies 目錄:

php artisan make:policy PostPolicy

注冊(cè)策略類(lèi) 策略類(lèi)生成后我們需要將其注冊(cè)到 Gate 類(lèi)。AuthServiceProvider 包含了一個(gè) policies 屬性來(lái)映射實(shí)體及管理該實(shí)體的策略類(lèi)。因此,我們指定 Post 模型的策略類(lèi)是 PostPolicy

<?php

namespace App\Providers;

use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider{
    /**
     * 應(yīng)用的策略映射
     *
     * @var array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];
}

4.2 編寫(xiě)策略

策略類(lèi)生成和注冊(cè)后,我們可以為授權(quán)的每個(gè)權(quán)限添加方法。例如,我們?cè)?PostPolicy 中定義一個(gè) update 方法,該方法判斷給定 User 是否可以更新某個(gè) Post

<?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;
    }
}

你可以繼續(xù)在策略類(lèi)中為授權(quán)的權(quán)限定義更多需要的方法,例如,你可以定義 show, destroy, 或者 addComment 方法來(lái)認(rèn)證多個(gè) Post 動(dòng)作。 注意:所有策略類(lèi)都通過(guò)服務(wù)容器進(jìn)行解析,這意味著你可以在策略類(lèi)的構(gòu)造函數(shù)中類(lèi)型提示任何依賴,它們將會(huì)自動(dòng)被注入。

4.3 檢查策略

策略類(lèi)方法的調(diào)用方式和基于授權(quán)回調(diào)的閉包一樣,你可以使用 Gate 門(mén)面,User 模型,@can 指令或者幫助函數(shù) policy。

通過(guò) Gate 門(mén)面 Gate 將會(huì)自動(dòng)通過(guò)檢測(cè)傳遞過(guò)來(lái)的類(lèi)參數(shù)來(lái)判斷使用哪一個(gè)策略類(lèi),因此,如果傳遞一個(gè) Post 實(shí)例給 denies 方法,相應(yīng)的,Gate 會(huì)使用 PostPolicy 來(lái)進(jìn)行動(dòng)作授權(quán):

<?php

namespace App\Http\Controllers;

use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;

class PostController extends Controller{
    /**
     * 更新給定文章
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $post = Post::findOrFail($id);

        if (Gate::denies('update', $post)) {
            abort(403);
        }

        // 更新文章...
    }
}

通過(guò) User 模型 User 模型的 cancannot 方法將會(huì)自動(dòng)使用給定參數(shù)中有效的策略類(lèi)。這些方法提供了便利的方式來(lái)為應(yīng)用接收到的任意 User 實(shí)例進(jìn)行授權(quán):

if ($user->can('update', $post)) {
    //
}

if ($user->cannot('update', $post)) {
    //
}

Blade 模板中的使用 類(lèi)似的,Blade 指令@can 將會(huì)使用參數(shù)中有效的策略類(lèi):

@can('update', $post)
    <!-- The Current User Can Update The Post -->
@endcan

通過(guò)幫助函數(shù) policy 全局的幫助函數(shù) policy 用于為給定類(lèi)實(shí)例接收策略類(lèi)。例如,我們可以傳遞一個(gè) Post 實(shí)例給幫助函數(shù) policy 來(lái)獲取相應(yīng)的 PostPolicy 類(lèi)的實(shí)例:

if (policy($post)->update($user, $post)) {
    //
}

5、控制器授權(quán)

默認(rèn)情況下,Laravel 自帶的控制器基類(lèi) App\Http\Controllers\Controller 使用了 AuthorizesRequeststrait,該 trait 提供了可用于快速授權(quán)給定動(dòng)作的 authorize 方法,如果授權(quán)不通過(guò),則拋出 HttpException 異常。

authorize 方法和其他多種授權(quán)方法使用方法一致,例如 Gate::allows$user->can()。因此,我們可以這樣使用 authorize 方法快速授權(quán)更新 Post 的請(qǐng)求:

<?php

namespace App\Http\Controllers;

use App\Post;
use App\Http\Controllers\Controller;

class PostController extends Controller{
    /**
     * 更新給定文章
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $post = Post::findOrFail($id);

        $this->authorize('update', $post);

        // 更新文章...
    }
}

如果授權(quán)成功,控制器繼續(xù)正常執(zhí)行;然而,如果 authorize 方法判斷該動(dòng)作授權(quán)失敗,將會(huì)拋出 HttpException 異常并生成帶 403 Not Authorized 狀態(tài)碼的 HTTP 響應(yīng)。正如你所看到的,authorize 方法是一個(gè)授權(quán)動(dòng)作、拋出異常的便捷方法。 AuthorizesRequeststrait 還提供了 authorizeForUser 方法用于授權(quán)非當(dāng)前用戶:

$this->authorizeForUser($user, 'update', $post);

自動(dòng)判斷策略類(lèi)方法 通常,一個(gè)策略類(lèi)方法對(duì)應(yīng)一個(gè)控制器上的方法,例如,在上面的 update 方法中,控制器方法和策略類(lèi)方法共享同一個(gè)方法名:update。 正是因?yàn)檫@個(gè)原因,Laravel 允許你簡(jiǎn)單傳遞實(shí)例參數(shù)到 authorize 方法,被授權(quán)的權(quán)限將會(huì)自動(dòng)基于調(diào)用的方法名進(jìn)行判斷。在本例中,由于 authorize 在控制器的 update 方法中被調(diào)用,那么對(duì)應(yīng)的,PostPolicyupdate 方法將會(huì)被調(diào)用:

/**
 * 更新給定文章
 *
 * @param  int  $id
 * @return Response
 */
public function update($id){
    $post = Post::findOrFail($id);

    $this->authorize($post);

    // 更新文章...
}