Laravel學習筆記六-許可權管理與中介軟體Middleware

Corwien發表於2019-02-16

這一節我們將給相關的動作頁面新增許可權,如已經登入的使用者將不會看到註冊、登入按鈕,更不會對別人的個人資料進行編輯操作,除非是管理員,這裡我們將藉助Laravel提供的中介軟體Middleware快速實現。

一、HTTP 中介軟體

HTTP 中介軟體提供了一個方便的機制來過濾進入應用程式的 HTTP 請求,例如,Laravel 本身使用中介軟體來驗證使用者的身份,如果使用者未通過身份驗證,中介軟體將會把使用者導向登入頁面,反之,當使用者通過了身份驗證,中介軟體將會通過此請求並接著往下執行。

當然,除了身份驗證之外,中介軟體也可以被用來執行各式各樣的任務,CORS 中介軟體負責替所有即將離開程式的響應加入適當的標頭。而日誌中介軟體則可以記錄所有傳入應用程式的請求。

Laravel 框架已經內建了一些中介軟體,包括維護、身份驗證、CSRF 保護,等等。所有的中介軟體都放在app/Http/Middleware 目錄內。

若是希望每個 HTTP 請求都經過一箇中介軟體,只要將中介軟體的類加入到 app/Http/Kernel.php 的 $middleware 屬性清單列表中。

// 在 AppHttpKernel 類內...
protected $routeMiddleware = [
    `auth` => AppHttpMiddlewareAuthenticate::class,
    `auth.basic` => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
    `guest` => AppHttpMiddlewareRedirectIfAuthenticated::class,];

Laravel 提供的 Auth 中介軟體在過濾指定動作時,如果該使用者未通過身份驗證,預設將會被重定向到 auth/login 登入頁面,但我們在應用中使用的登入頁面地址是 /login,因此我們需要對 Auth 中介軟體預設的重定向地址進行更改。

app/Http/Middleware/Authenticate.php

<?php

namespace AppHttpMiddleware;
// 
// 

class Authenticate
{
  // 
  // 
    public function handle($request, Closure $next)
    {
        if ($this->auth->guest()) {
            if ($request->ajax()) {
                return response(`Unauthorized.`, 401);
            } else {
                return redirect()->guest(`login`);
            }
        }

        return $next($request);
    }
}

通過閱讀 Auth 中介軟體的原始碼可知,Auth 中介軟體會先判斷當前使用者是否為遊客(未登入狀態的使用者),當使用者為遊客且請求方式是 ajax 時,則丟擲一個 401 響應資訊,如果不是通過 ajax 的方式請求,則重定向到登入頁面。最後,如果使用者為已登入狀態,則接著執行下一個請求。

現在退出登入,再次嘗試訪問 http://sample.app/users/1/edit 頁面將會被重定向到登入頁面。

二、授權策略 (Policy)

在完成對使用者未登入限制之後,我們來研究下已登入使用者的許可權驗證,即只有使用者自己才能編輯自己的個人資訊,其他使用者無權編輯。
在 Laravel 中可以使用 授權策略(Policy) 來對使用者的操作許可權進行驗證,在使用者未經授權進行操作時將返回 403 異常。

我們可以使用以下命令來生成一個名為 UserPolicy 的授權策略類檔案,用於管理使用者模型的授權。

$ php artisan make:policy UserPolicy

什麼是授權策略呢?我們一般在個人資料編輯時,需要驗證是否為使用者自己,這樣才有許可權修改,先查出使用者的個人資訊,然後再和登入的使用者ID判斷是否為同一個人,而Laravel為我們提供了一套授權機制,只需新建一個授權策略,然後將其直接呼叫:

$ php artisan make:policy UserPolicy


<?php

namespace AppPolicies;

use IlluminateAuthAccessHandlesAuthorization;
use AppModelsUser;

class UserPolicy
{
    use HandlesAuthorization;

    public function update(User $currentUser, User $user)
    {
        return $currentUser->id === $user->id;
    }
}

update 方法接收兩個引數,第一個引數預設為當前登入使用者例項,第二個引數則為要進行授權的使用者例項。當兩個 id 相同時,則代表兩個使用者是相同使用者,使用者通過授權,可以接著進行下一個操作。如果 id 不相同的話,將丟擲 403 異常資訊來拒絕訪問。

使用授權策略需要注意以下兩點:

我們並不需要檢查 $currentUser 是不是 NULL。未登入使用者,框架會自動為其 所有許可權 返回 false;
呼叫時,預設情況下,我們 不需要 傳遞當前登入使用者至該方法內,因為框架會自動載入當前登入使用者(接著看下去,後面有例子);
接下來我們還需要在 AuthServiceProvider 類中對授權策略進行設定。AuthServiceProvider 包含了一個 policies 屬性,該屬性用於將各種模型對應到管理它們的授權策略上。我們需要為使用者模型 User 指定授權策略 UserPolicy。

app/Providers/AuthServiceProvider.php

<?php

namespace AppProviders;

use IlluminateContractsAuthAccessGate as GateContract;
use IlluminateFoundationSupportProvidersAuthServiceProvider as ServiceProvider;

use AppModelsUser;
use AppPoliciesUserPolicy;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        `AppModel` => `AppPoliciesModelPolicy`,
        User::class  => UserPolicy::class,
    ];

    /**
     * Register any application authentication / authorization services.
     *
     * @param  IlluminateContractsAuthAccessGate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        $this->registerPolicies($gate);

        //
    }
}

授權策略定義完成之後,我們便可以通過在使用者控制器中使用 authorize 方法來驗證使用者授權策略。預設的 AppHttpControllersController 類包含了 Laravel 的 AuthorizesRequests trait。此 trait 提供了 authorize 方法,它可以被用於快速授權一個指定的行為,當無許可權執行該行為時會丟擲 HttpException。authorize 方法接收兩個引數,第一個為授權策略的名稱,第二個為進行授權驗證的資料。

我們需要為 edit 和 update 方法加上這行:

$this->authorize(`update`, $user);

書寫的位置如下:

app/Http/Controllers/UsersController.php

<?php

namespace AppHttpControllers;
.
.
.
class UsersController extends Controller
{
    .
    .
    .
    public function edit($id)
    {
        $user = User::findOrFail($id);
        $this->authorize(`update`, $user);
        return view(`users.edit`, compact(`user`));
    }

    public function update($id, Request $request)
    {
        $this->validate($request, [
            `name` => `required|max:50`,
            `password` => `confirmed|min:6`
        ]);

        $user = User::findOrFail($id);
        
         // 授權策略判斷
        $this->authorize(`update`, $user);

        $data = array_filter([
            `name` => $request->name,
            `password` => $request->password,
        ]);
        $user->update($data);

        session()->flash(`success`, `個人資料更新成功!`);

        return redirect()->route(`users.show`, $id);
    }
}

現在,如果你使用 id 為 1 的使用者去訪問 id 為 2 的使用者編輯頁面,將丟擲 403 異常資訊。

三、PHP中的Trait 特性及作用

PHP中的Trait 特性及作用

Traits 是一種為類似 PHP 的單繼承語言而準備的程式碼複用機制。Trait 為了減少單繼承語言的限制,使開發人員能夠自由地在不同層次結構內獨立的類中複用方法集

簡單使用
首先,當然是宣告個 Trait,PHP5.4 增加了 trait 關鍵字

trait first_trait {
function first_method() { /* Code Here */ }
function second_method() { /* Code Here */ }
}

同時,如果要在 Class 中使用該 Trait,那麼使用 use 關鍵字

class first_class {
// 注意這行,宣告使用 first_trait
use first_trait;
}
$obj = new first_class();
// Executing the method from trait
$obj->first_method(); // valid
$obj->second_method(); // valid

1.使用多個 Trait

trait first_trait
{
function first_method() { echo "method"; }
}
trait second_trait {
function second_method() { echo "method"; }
}
class first_class {
// now using more than one trait
use first_trait, second_trait;
}
$obj= new first_class();
// Valid
$obj->first_method(); // Print : method
// Valid
$obj->second_method(); // Print : method

2. Trait 中宣告抽象方法

我們可以在 Trait 中宣告需要實現的抽象方法,這樣能使使用它的 Class 必須實現它

trait first_trait {
function first_method() { echo "method"; }
// 這裡可以加入修飾符,說明呼叫類必須實現它
abstract public function second_method();
}
class first_method {
use first_trait;
function second_method() {
/* Code Here */
}
}

Laravel中也應用了許多Trait方法

<?php

namespace IlluminateAuthAccess;

trait HandlesAuthorization
{
    /**
     * Create a new access response.
     *
     * @param  string|null  $message
     * @return IlluminateAuthAccessResponse
     */
    protected function allow($message = null)
    {
        return new Response($message);
    }

    /**
     * Throws an unauthorized exception.
     *
     * @param  string  $message
     * @return void
     *
     * @throws IlluminateAuthAccessUnauthorizedException
     */
    protected function deny($message = `This action is unauthorized.`)
    {
        throw new UnauthorizedException($message);
    }
}

Laravel中使用上邊定義好的Trait方法:
AppPoliciesUserPolicy

<?php

namespace AppPolicies;

use IlluminateAuthAccessHandlesAuthorization;
use AppModelsUser;

class UserPolicy
{
    use HandlesAuthorization;

    /**
     * Create a new policy instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }


    /**
     * 使用者更新時的許可權驗證
     * @param User $currentUser
     * @param User $user
     * @return bool
     */
    public function update(User $currentUser, User $user)
    {
        return $currentUser->id === $user->id;

    }
}

相關文章:
我所理解的 PHP Trait

相關文章