Laravel 核心--深入剖析 Laravel 框架 Jwt 元件的實現原理

yefy發表於2020-12-28

jwt 是什麼?

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

為什麼不使用 session,而直接使用jwt?

  • session 儲存在伺服器端,而 Jwt 儲存在客戶端。
  • session 方式儲存使用者資訊的最大問題在於要佔用伺服器記憶體,增加伺服器的開銷,而 Jwt 方式將使用者狀態分散到了客戶端中,可以明顯減輕伺服器的記憶體壓力。

如何使用

1、composer 安裝

composer require tymon/jwt-auth

2、註冊服務提供者,laravel 框架版本小於5.4時,需要手動在 config/app.php 檔案中註冊服務提供者

'providers' => [

    ...

    Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]

3、生成配置檔案,執行以下命令

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

4、生成 jwt 金鑰

php artisan jwt:secret

你也可以在 .env 檔案中修改金鑰,比如 JWT_SECRET=xxxx

5、修改 auth.php 檔案中的配置,修改 Laravel 框架預設的 auth 驅動

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

...

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model'  => App\Models\User\User::class
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

上述配置,表示 auth 驅動使用 jwt 的方式,使用者提供者使用 users,使用者提供者支援 databaseeloquent 兩種方式

6、修改 User 模型,實現 getJWTIdentifier()getJWTCustomClaims() 方法

<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    // Rest omitted for brevity

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

7、新增控制器認證中介軟體

    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login']]);
    }

上述程式碼表示,每次請求都需要經過 auth 這個中介軟體,登入方法除外

常用方法

官方文件寫的很清楚,這裡直接給出官方的說明文件,地址如下:

jwt-auth.readthedocs.io/en/develop...

原始碼解析 (重點)

這裡會對原始碼進行分析,預設你瞭解一些基本的知識和框架的執行流程,比如容器、依賴注入、門面模式和中介軟體等知識,這裡不會對這些知識和內容進行過多的闡述,如果有不懂和不理解的地方,歡迎評論,大家系好安全帶,老司機要發車了!!!

使用者鑑權

當訪問控制器中某個方法時,會先經過 Authenticate 中介軟體進行處理

    //1.1 經過 handle 方法處理請求
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        return $next($request);
    }
    //1.2 經過 authenticate 方法檢查使用者是否登入
     protected function authenticate($request, array $guards)
        {
            if (empty($guards)) {
                $guards = [null];
            }

            foreach ($guards as $guard) {
                // 訪問 JwtGuard 中的 check 方法 
                if ($this->auth->guard($guard)->check()) { // $this->auth->guard($guard) 會生成JwtGuard物件 
                    return $this->auth->shouldUse($guard);
                }
            }

            $this->unauthenticated($request, $guards);
        }
    //1.3 訪問程式碼塊 GuardHelpers 中的 check 方法,校驗使用者是否登入
    public function check()
    {
        return ! is_null($this->user());
    }
    //1.4 訪問 JwtGuard 中的 user 方法 
     public function user()
    {
        if ($this->user !== null) {
            return $this->user;
        }
        //獲取請求頭中的 token,校驗 token 是否過期或者在黑名單中,具體的程式碼請檢視原始碼
        if ($this->jwt->setRequest($this->request)->getToken() &&
            ($payload = $this->jwt->check(true)) &&
            $this->validateSubject()
        ) {
           //根據負載 payload 獲取使用者的資訊,然後返回
            return $this->user = $this->provider->retrieveById($payload['sub']);
        }
    }
    //1.5 建立使用者模型,根據主鍵獲取使用者資訊並且返回
    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $this->newModelQuery($model)
                    ->where($model->getAuthIdentifierName(), $identifier)
                    ->first();
    }

至此,使用者鑑權的核心流程就分析完畢了,這裡只給出了核心程式碼,需要大家根據上述程式碼去檢視原始碼,只有知其然知其所以然,才可以放心大膽的使用擴充套件包,出現問題也才可以及時的發現和解決,下車!!!

本作品採用《CC 協議》,轉載必須註明作者和本文連結
yefy

相關文章