Middleware 介紹 & 馬上就可以開始的懶人設定!

如果以前沒有任何 framework 的經驗,對於 middleware 的概念一開始可能會一時無法理解(強者不在討論範圍內)

去找文章或什麼的一定講過 Middleware 的好處及為何要存在,這邊就以我的理解(不一定正確)再講一遍

把跟 controller 要做的「正事」的程式碼跟其他「雜七雜八」像是驗證的程式碼分開,這樣寫扣的人跟追扣的人都看得比較清楚,皆大歡喜。

  • 「正事」「controller 的業務邏輯」例如說修改 DB 資料等等,處理 client 的 request 內
  • 「雜七雜八」就像是 session 的處理、驗證是不是已登入的使用者、檢查 CSRF token 等等的

Laravel 原生就有寫好的 Auth middleware 放在那裏可以直接用(ㄔㄠ)。照著官網說的,不用在原先只有寫好「正事」業務邏輯的 controller 不加任何東西,在 routes/web.php 裡面這樣用就可以驗證了,真的是非常的方便又懶人。

Route::get('profile', 'ProfileController@show')->middleware('auth');

// 這是另外一種寫法
Route::get('profile', [
       'uses' => 'ProfileController@show',
       'middleware' => ['auth'] // 為什麼這邊可以丟一個 array 呢?1 個 middleware 用不夠,你有用第二個嗎?
]);

東西當然是趕快看起來正常動起來才能交差嘛!如果連動都動不起來的話還了解背後怎麼做的了解辛酸的嗎?
所以這裡也先講一下怎樣可以讓東西快速動起來的懶人包(?),以做 user permission check 為例子

以下這篇文章以 laravel 5.4 為基礎

1. 懶惰的人當然是用別人的 package (entrust)

首先先來個 entrust 傳送門:https://github.com/Zizaco/entrust

Laravel 原本的 Auth 可以檢查是不是已登入使用者,Entrust 可以進一步設定哪種身分(role)可以做什麼事情(permission),當我們把每一個帳號都指定一個 Role,就可以輕鬆的知道這個帳號有哪些權限了

舉個例子:
Role "normal user" 可以做 create、edit
Role "admin" 可以做 create、edit、delete
把 "Andy" 這個帳號的 Role 設定為 normal user,他就不能執行 delete 的動作

發現部分的懶人包越寫越長,下面留的是跟 middleware 比較相關的設定,step by step 的詳細內容移到另外一篇 entrust 設定

A) 要使用 middleware,就要在 app/Http/Kernel.php 中的 routeMiddleware 加上這幾行

    'role' => \Zizaco\Entrust\Middleware\EntrustRole::class,
    'permission' => \Zizaco\Entrust\Middleware\EntrustPermission::class,
    'ability' => \Zizaco\Entrust\Middleware\EntrustAbility::class,
B) 在 routing 裡面加入 middleware

先看個 routing 範例

Route::get('/posts/create', [
        'uses' => 'PostsController@create',
        'middleware' => ['ability:admin|normal_user,create,false']
]);
  • 這個 middleware 要驗證的是登入的帳號必須具有 admin 或 normal_user 的 role 之一,又或著是擁有 create 這個 permission 的人才可以通過並真正進入 controller 執行,否則就會回傳 HTTP 403(最後一個參數 false 後面會說)
  • 原本的 middleware array 裡面填的是 'auth',現在填的是 'ability:...',表示現在用的是 ability middleware
  • 看看後面有參數,表示 middleware 的處理是可以接參數的
  • 而這個 ability middleware 是哪裡 include 的呢?在 I. 安裝的 6) 裡面,app/Http/Kernel.php 裡面的 routeMiddleware 裡面的 'ability' => \Zizaco\Entrust\Middleware\EntrustAbility::class, 這一行,然後看看安裝的時候另外加的兩行,不難了解額外還有 Role 和 Permission 另外兩個 middleware 其實也可以用

在 5.4 版,middleware 可以加參數,格式是長這樣

'funct_name:argu_1,argu_2,argu_3'

ability 這個 function 的 source code 長這樣

        /**
         * Handle an incoming request.
         *
         * @param \Illuminate\Http\Request $request
         * @param Closure $next
         * @param $roles
         * @param $permissions
         * @param bool $validateAll
         * @return mixed
         */
        public function handle($request, Closure $next, $roles, $permissions, $validateAll = false)
        {
                if ($this->auth->guest() || !$request->user()->ability(explode('|', $roles), explode('|', $permissions), array('validate_all' => $validateAll))) {
                        abort(403);
                }

                return $next($request);
        }
  • 前兩個參數是 role 跟 permission,可以指定這個 middleware 要驗證 user 是哪個 role 或擁有哪個 permission
  • 前兩個參數可以用 or | 來分開,如上面的範例:role 必須是 admin 或 normal_user 之中其一
  • 如果是必須要兩個 role 都符合(一個 user 可以 attach 多種 role),ability 沒有提供這個功能,直接在 middleware array 裡面多丟幾個 ability middleware 就好
  • 最後一個參數是要驗證前面 role 和 permission 參數中,全部的 role 跟 permission 才可以通過
    • true: 都要擁有
    • false: 擁有這些之中的其一就好
  • handle 裡面的 if clause 裡面就是各種檢查,然後檢查沒過就 raise HTTP 403,檢查過了 request 傳到下一個地方去做要做的事(參考後面的 Middleware 圖解觀念篇)

2. 別人的 package 不符合我的要求該怎麼辦?只好辛苦一點重寫一個 Method (handle)

entrust 提供的 role based permission checking 算很符合大多數人的需求了,但是有時候我們想要的 entrust 不一定能提供我們這種功能,例如像是

  • 我沒登入不小心按到編輯文章,我希望網站給我跳一個登入頁面,而不是醜醜的 HTTP 403 error
  • 使用者 Andy 跟使用者 Bill 都擁有 edit 文章的 permission,但是我不希望 Andy 編輯 Bill(非本人)的文章該怎麼辦?entrust 並沒有確認文章 owner 是哪個 userid 的功能
  • 再者,我希望 posts 只能被本人更改,但是 sysadmin 要可以改所有的東西,這種複雜的邏輯要怎麼辦?

接下來就以實作第一個希望 Redirection 到 login 頁面的需求對這個 handle methood 做修改

1) 使用者自訂的 Middleware 都放在 app/Http/Middleware,在這裡新增一個檔案 EntrustAbilityWithRedirection.php

<?php

namespace App\Http\Middleware;

use Zizaco\Entrust\Middleware\EntrustAbility as EntrustAbility;
use Closure;

class EntrustAbilityWithRedirection extends EntrustAbility
{
    public function handle($request, Closure $next, $roles, $permissions, $validateAll = false)
    {

        if ($this->auth->guest()){
            return redirect('/login');
        }

        if (!$request->user()->ability(explode('|', $roles), explode('|', $permissions), array('validate_all' => $validateAll))) {
            abort(403);
        }

        return $next($request);
    }
}
  • 這裡是繼承原本的 EntrustAbility 來 override handle 達到我需要 customize handle method 的目的
  • 新的 handle function interface 必須要跟舊的 compatible
  • 照抄當然要引入他的 class:use Zizaco\Entrust\Middleware\EntrustAbility as EntrustAbility;
  • 在這裡只是很簡單的把 if 判斷是 guest 的部分拉出去改成 redirect 到 login 頁面,其他維持不變,這樣就好了

2) 改 app/Http/Kernel.php,把 ability 這個 alias 原本引用的 library 的 class 換成剛剛新加的。這樣 route 裡面用的 ability 才是我們剛剛新寫的東西

        //'ability' => \Zizaco\Entrust\Middleware\EntrustAbility::class,
        // inherit ability class to use custom redirection
        'ability' => \App\Http\Middleware\EntrustAbilityWithRedirection::class,

3. 會抄 code 的話抄著抄著自己也會寫一個了(寫一個 Method handle)

這邊就是官網的自己寫一個簡單 Middleware 的教學,我搬過來這樣

1) 很熟 laravel 的懶人當然要 class structure 都給別人生啊

php artisan make:middleware CheckAge

2) 然後去 app/Http/Middleware/CheckAge.php 把 handle function 補完

$next($request) 表示是進行下個步驟的意思

<?php

namespace App\Http\Middleware;

use Closure;

class CheckAge
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->age <= 200) {
            return redirect('home');
        }

        return $next($request);
    }

}

3) 去 app/Http/Kernel.php 補上一行來 register 剛剛寫的 Checkage

global middleware 是所有 request 都一定會過這個 middleware。我講一下 routeMiddleware,跟上面的用法比較像,設定如下,在最後加一個 CheckAge 那一行

protected $routeMiddleware = [
    'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'CheckAge' => \App\Http\Middleware\CheckAge::class,
];

然後 routes/web.php 裡面要怎麼用 middleware 就看前面這樣。

至此懶人包大概就這樣,照著設定應該就會正常的...動...吧(?)至少我可以啦

results matching ""

    No results matching ""