Wake Liu
Wake Liu
Aug 10 · 8 min read
Photo by Neil Martin on Unsplash
Lumen Auth、Guard、User 身份驗證詳解 文章目錄:(一)Authentication 文件忙什麼
(二)你的架構不是你的架構
(三)全部的流程都解開了!(上)

相比於大型框架或系統,我個人更偏好小巧、微型的可替換式套件,像 Idiorm & ParisPhinx 這種,一來架構易於理解,抽象化的層次少,二來對專案系統而言,各功能的耦合度也降低,必要的時候可直接替換調整(例如某個套件不維護了)。

當然缺點就是什麼都要自己手動整合,獨立套件可能和核心系統的相容性沒那麼好,或是套件之間彼此有所衝突(像是相依了不同版本的套件),而且一個專案內的程式脈落會有很多不同的設計思維,需要一一去瞭解。

即使如此,目前幾大主流框架的設計思維還是十分值得參考借鏡,一套系統成長到一定規模後,勢必要藉由架構上的抽象化、介面化來做整理和增加彈性,這是不可避免的發展路徑。

Auth、Guard、User 的流程實務

回到主題,我們接下來要詳細走過一次身份驗證的完整旅程,圖上我補充了 Lumen 和 AuthServiceProvider 兩部份,讓關聯性更加清楚。

Lumen → ServiceProvider → AuthManager

要在 Lumen 系統啟用身份驗證功能,我們需要將先 AuthServiceProvider 註冊到 Lumen Application — 也就是 $app 當中,這同時也是流程的起點:

$app->register (\App\Providers\AuthServiceProvider::class);

AuthServiceProvider 是怎麼將相關服務綁定到系統中的?檢視原始碼,可以從 boot function 看到以下:

$this->app['auth']->viaRequest ('api', function ($request) {
...
});

首先這裡要求取得 $this->app['auth']app['auth'] 是一個本來不存在的物件,經由存取時的觸發,進而實例化了 AuthManager,但為什麼是 Illuminate\Auth\AuthManager 呢?這是由核心程式 Illuminate\Auth\AuthServiceProvider 所指定的,具體的過程如下:

  1. 程式中存取 app['auth']
  2. app 自我檢查是否 auth 已經建立,如果尚未建立則進行實例化。
  3. 因為 Lumen Application — Laravel\Lumen\Application 當中有綁定 auth 為特殊關鍵字,要進行 auth 的實例化時會觸發 registerAuthBindings 函式。
  4. registerAuthBindings 函式將核心服務 Illuminate\Auth\AuthServiceProvider 進行註冊。
  5. Illuminate\Auth\AuthServiceProviderregister function 中,透過單例模式 app->singletonIlluminate\Auth\AuthManager 類別實例化並賦予給 app['auth']

所以除了呼叫 app['auth'] 和簡單加工, App\Providers\AuthServiceProvider 沒特別載入什麼服務內容
,主要還是由核心的 auth 相關綁定進行的處理。

後面一段程式碼 app['auth']->viaRequest 因為和 Guard 有關,我們在下節一併說明。

AuthManager → XXX (Guard) → GuardDriver → Guard

那麼 AuthManager 是如何和 Guard 進行互動的?在 Lumen 系統中,共內建了三種 Guard 驗證類別:

  • RequestGuard:基於連線請求,提供擴充用的驗證模式,必須在物件建立時傳入 callback 函式;此 cb 函式會收到 $requestuserProvider 兩個傳入變數,並要求回傳 可用的使用者物件 (不是回傳驗證結果而已)。
  • TokenGuard:同樣基於連線請求,針對傳入內容進行驗證的模式,例如 header 中是否帶有特定的 token 或 password。
  • SessionGuard:依據 Session(和 Cookie)的資料進行驗證模式,也就是一般 PHP 程式中,紀錄 / 識別使用者登入所採用的方式。

app['auth']AuthManager 實例化以後,我們即可透過它來取得上述的 Guard 驗證物件(或是你的自定義 Guard):

  1. 呼叫 app['auth']->guard ('XXX') ,如果對應名稱 XXX 的 Guard 物件已存在則回傳,若否,準備建立 Guard 物件;要注意的是沒有帶入名稱時,會自動使用 config/auth.php 中的 defaults.guard
  2. 建立 Guard 物件前,會先檢查 config/auth.phpguards 裡是否有 XXX Guard 的相關設定,沒有的話會拋出錯誤。
  3. 綜合 XXX Guard 的設定資料,依序檢查此 Guard 是否有:
    ● 自定義的 callback 建立函式:在程式中經由 app['auth']->extend () 以 callback 進行定義的建立函式,必須同時在設定檔中設定此定義的名稱。
    ● AuthManager 內的 method 驅動函式:如果有自行擴充 AuthManager 的話,可以定義 createXXXDriver 的 method 來驅動 XXX Guard。內建 Guard 的 預設的驅動函式 也是同樣方式,例如 SessionGuardcreateSessionDriver,TokenGuard 則是 createTokenDriver
  4. 呼叫 建立函式 / 驅動函式 實例化目標 Guard,並將物件以 XXX 為索引存入 AuthManager 自身 guards array 裡。
  5. 回傳 Guard 物件。

從上面的流程可以知道,如果你要自定一個 Guard ,最簡單的方式就是透過 app['auth']->extend () 定義你 Guard 的啟動函式,並在 config/auth.php 中寫好設定。

回頭看 app['auth']->viaRequest ,其實 viaRequest 是基於 RequestGuardextend () 延伸 ;白話來講,就是幫你建立 RequestGuard 物件,並且提供傳入 user callback 的函式。我們看一下這兩段程式:

AuthServiceProvider 的 boot function

$this->app['auth']->viaRequest ('api', function ($request) {
if ($request->input ('api_token')) {
return User::where('api_token'
, $request->input('api_token'))->first();
}
});

AuthManager 的 viaRequest

public function viaRequest ($driver, callable $callback) {
return $this->extend ($driver, function () use ($callback) {
$guard = new RequestGuard ($callback
, $this->app['request']
, $this->createUserProvider ());
$this->app->refresh ('request', $guard, 'setRequest'); return $guard;
});
}

AuthServiceProvider 啟動時, viaRequest () 經由 extend () 定義了名為 api 的 建立 / 驅動函式,並且傳入一個 從連線請求中的 api_token 比對資料庫取得使用者 的 callback 作為 RequestGuard 的建構參數,等下一次呼叫 app['auth']->guard ('api') 時就會進行實例化,並按 callback 回傳使用者物件。

結語

AuthManager 大概是身份驗證架構中,扮演角色最複雜的物件了;除了原本功能外,它還透過 __call 實做了對應 Guard 的動態呼叫,也就是說可以直接 app['auth']->guardFunc () 替代 app['auth']->guard ()->guardFunc (),真的讓人很難回追架構啊…

這系列文感覺越寫越多,下一篇我們會繼續深入 AuthManager 和 UserProvider 之間的關聯。謝謝各位的閱讀,我們下次見。

興趣使然的程式猿

紀錄各種因愛而行的人生道路

Wake Liu

Written by

Wake Liu

興趣使然的程式猿

興趣使然的程式猿

紀錄各種因愛而行的人生道路

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade