學習 Lumen 使用者認證 (一)

coding01發表於2017-09-29

好久沒寫 PHP 程式碼了,尤其是 Lumen,我是 Lumen 的忠實使用者,自從面世開始,我就將 Lumen 作為我 API 的主要框架使用。

但說到 API,不得不說的一個概念:「前後端分離」,現在越來越多的團隊都採用前後端分離,徹底解放出前端的優勢,也讓後臺更加集中於資料的輸出。關於這方面的討論,不在這裡討論了,可以參考一些文章深入研究:

segmentfault.com/a/119000000…

正因為有了前後端分離,後臺關注於介面 API 的輸出,當時 Lumen 的出現,就是為 RESTful API 而生的:

Decidedly Laravel. Delightfully Minimal.

Lightning fast micro-services and APIs delivered with the elegance you expect.

將 Lumen 作為介面框架使用,不得不解決一個核心問題:如何對訪問者進行「認證」。

使用者認證

Lumen 雖然與 Laravel 使用了相同的底層類庫實現,但是因 Lumen 面向的是無狀態 API 的開發,不支援 session,所以預設的配置不同。Lumen 必須使用無狀態的機制來實現,如 API 令牌(Token)。

我們看看 Lumen 官網提供的例子:

use Illuminate\Http\Request;

$app->get('/post/{id}', ['middleware' => 'auth', function (Request $request, $id) {
    $user = Auth::user();

    $user = $request->user();

    //
}]);複製程式碼

其中使用了中介軟體:'middleware' => 'auth',我們看看 auth 中介軟體函式:

$app->routeMiddleware([
     'auth' => App\Http\Middleware\Authenticate::class,
 ]);複製程式碼

關聯的是 Authenticate 類,我們看 Authenticate 的 handle 函式:

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if ($this->auth->guard($guard)->guest()) {
            return response('Unauthorized.', 401);
        }

        return $next($request);
    }複製程式碼

首先會判斷$this->auth->guard($guard)->guest()。我們繼續跟進程式碼到 AuthManager 中:

/**
     * Attempt to get the guard from the local cache.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return isset($this->guards[$name])
                    ? $this->guards[$name]
                    : $this->guards[$name] = $this->resolve($name);
    }複製程式碼

預設傳入的 $name = null,所以我們看看 $this->getDefaultDriver()

/**
     * Get the default authentication driver name.
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }複製程式碼

這就到了預設的配置 config 中了:

從 Lumen 原始碼中可以看出 Lumen 的預設認證方式「api」。

我們再來看看 Laravel 的預設認證方式:

Laravel 預設採用「web」方式,而 web 方式是使用 session 來進行使用者認證。這也就很好的說明了 Lumen 的無狀態性。

接著我們需要明白 Lumen 如何通過「api」來進行使用者認證的。

AuthServiceProvider 存放在 app/Providers 資料夾中,此檔案中只有一個 Auth::viaRequest 呼叫。viaRequest 會在系統需要認證的時候被呼叫,此方法接受一個匿名函式傳參,在此匿名函式內,你可以任意的解析 App\User 並返回,或者在解析失敗時返回 null,如:

/**
     * Boot the authentication services for the application.
     *
     * @return void
     */
    public function boot()
    {
        // Here you may define how you wish users to be authenticated for your Lumen
        // application. The callback which receives the incoming request instance
        // should return either a User instance or null. You're free to obtain
        // the User instance via an API token or any other method necessary.

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

我們來看看 viaRequest 函式:

/**
     * Register a new callback based request guard.
     *
     * @param  string  $driver
     * @param  callable  $callback
     * @return $this
     */
    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;
        });
    }複製程式碼

這裡關鍵的是 RequestGuard,這個類的核心函式:

/**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    {
        // If we've already retrieved the user for the current request we can just
        // return it back immediately. We do not want to fetch the user data on
        // every call to this method because that would be tremendously slow.
        if (! is_null($this->user)) {
            return $this->user;
        }

        return $this->user = call_user_func(
            $this->callback, $this->request, $this->getProvider()
        );
    }複製程式碼

這個是判斷是否獲取使用者資訊,主要是呼叫callback 函式,而這個函式就是我們從 viaRequest 傳入的:

function ($request) {
    if ($request->input('api_token')) {
                return User::where('api_token', $request->input('api_token'))->first();
            }
        }複製程式碼

而這只是舉一個驗證使用者的例子,判斷請求是否傳入 api_token引數,並通過 User Model 直接匹配查詢獲取 User or null。

當然在實際開發中,我們不能只是簡單的獲取 api_token直接關聯資料庫查詢使用者資訊。

在 API 開發中,使用者認證是核心,是資料是否有保障的前提,目前主要有兩種常用方式進行使用者認證: JWT 和 OAuth2。

下一步

當前只是對 Lumen 的「使用者認證」進行簡單的瞭解,下一步通過對 「JWT」的學習,來看看如何利用 JWT 來有效的使用者認證,更加安全的保障介面資訊被有效的使用者訪問。

附:
Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基於JSON 的開放標準 (RFC 7519)。該 token 被設計為緊湊且安全的,特別適用於分散式站點的單點登入(SSO)場景。JWT 的宣告一般被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊,該 token 也可直接被用於認證,也可被加密。

「未完待續」


coding01 期待您繼續關注

qrcode
qrcode

相關文章