Lumen Auth、Guard、User 身份驗證詳解 文章目錄:(一)Authentication 文件忙什麼
(二)你的架構不是你的架構
(三)全部的流程都解開了!(上)
相比於大型框架或系統,我個人更偏好小巧、微型的可替換式套件,像 Idiorm & Paris、Phinx 這種,一來架構易於理解,抽象化的層次少,二來對專案系統而言,各功能的耦合度也降低,必要的時候可直接替換調整(例如某個套件不維護了)。
當然缺點就是什麼都要自己手動整合,獨立套件可能和核心系統的相容性沒那麼好,或是套件之間彼此有所衝突(像是相依了不同版本的套件),而且一個專案內的程式脈落會有很多不同的設計思維,需要一一去瞭解。
即使如此,目前幾大主流框架的設計思維還是十分值得參考借鏡,一套系統成長到一定規模後,勢必要藉由架構上的抽象化、介面化來做整理和增加彈性,這是不可避免的發展路徑。
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 所指定的,具體的過程如下:
- 程式中存取
app['auth']
。 app
自我檢查是否auth
已經建立,如果尚未建立則進行實例化。- 因為 Lumen Application — Laravel\Lumen\Application 當中有綁定
auth
為特殊關鍵字,要進行auth
的實例化時會觸發registerAuthBindings
函式。 registerAuthBindings
函式將核心服務 Illuminate\Auth\AuthServiceProvider 進行註冊。- Illuminate\Auth\AuthServiceProvider 在 register function 中,透過單例模式
app->singleton
將 Illuminate\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 函式會收到
$request
和userProvider
兩個傳入變數,並要求回傳 可用的使用者物件 (不是回傳驗證結果而已)。 - TokenGuard:同樣基於連線請求,針對傳入內容進行驗證的模式,例如 header 中是否帶有特定的 token 或 password。
- SessionGuard:依據 Session(和 Cookie)的資料進行驗證模式,也就是一般 PHP 程式中,紀錄 / 識別使用者登入所採用的方式。
當 app['auth']
— AuthManager 實例化以後,我們即可透過它來取得上述的 Guard 驗證物件(或是你的自定義 Guard):
- 呼叫
app['auth']->guard ('XXX')
,如果對應名稱 XXX 的 Guard 物件已存在則回傳,若否,準備建立 Guard 物件;要注意的是沒有帶入名稱時,會自動使用 config/auth.php 中的defaults.guard
。 - 建立 Guard 物件前,會先檢查 config/auth.php 的
guards
裡是否有 XXX Guard 的相關設定,沒有的話會拋出錯誤。 - 綜合 XXX Guard 的設定資料,依序檢查此 Guard 是否有:
● 自定義的 callback 建立函式:在程式中經由app['auth']->extend ()
以 callback 進行定義的建立函式,必須同時在設定檔中設定此定義的名稱。
● AuthManager 內的 method 驅動函式:如果有自行擴充 AuthManager 的話,可以定義 createXXXDriver 的 method 來驅動 XXX Guard。內建 Guard 的 預設的驅動函式 也是同樣方式,例如 SessionGuard 是 createSessionDriver,TokenGuard 則是 createTokenDriver。 - 呼叫 建立函式 / 驅動函式 實例化目標 Guard,並將物件以 XXX 為索引存入 AuthManager 自身
guards array
裡。 - 回傳 Guard 物件。
從上面的流程可以知道,如果你要自定一個 Guard ,最簡單的方式就是透過 app['auth']->extend ()
定義你 Guard 的啟動函式,並在 config/auth.php 中寫好設定。
回頭看 app['auth']->viaRequest
,其實 viaRequest
是基於 RequestGuard 的 extend ()
延伸 ;白話來講,就是幫你建立 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(); }
});
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 之間的關聯。謝謝各位的閱讀,我們下次見。