[TOC] #### 1. 前言 --- firebase/php-jwt 是一個非常簡單的 JWT 庫,用于在 PHP 中對 JSON Web令牌(JWT)進行編碼和解碼 packagist 上的下載次數更是達到了 1億 以上,可見該擴展包受歡迎的程度 本文記錄使用 ThinkPHP6.0 開發(fā)微信小程序接口時如何使用 JWT 做的接口鑒權 ``` composer create-project topthink/think:"6.0.*" cd think composer require firebase/php-jwt:"6.x" ``` 觀看本文前首先要明白一個概念: TP6.0 中控制器的構造方法、控制器中間件的執(zhí)行順序 ``` 控制器構造方法 > 控制器中間件 > 控制器方法 ``` #### 2. 過期時間 --- 在 **\Firebase\JWT\JWT::decode()** 方法中,可以發(fā)現(xiàn)以下代碼 當 $payload 中有 exp 屬性時,則判斷 token 是否過期 當 $payload 中沒有傳入 exp 屬性時,則 token 可以永久使用 ``` // Check if this token has expired. if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { throw new ExpiredException('Expired token'); } ``` #### 3. 代碼示例 --- 公共基礎控制器構造方法 Base.php ```php protected $middleware = [JwtMiddleware::class]; public function __construct(Request $request) { $token = $request->header('token'); if (empty($token)) { $request->uid = 0; } else { $request->uid = JwtAuth::decode($token); } } ``` 創(chuàng)建中間件 JwtMiddleware.php ``` public function handle($request, \Closure $next) { // 因為構造方法優(yōu)先于控制器中間件執(zhí)行 // 如果 $request->uid 已存在,代表已在構造方法中獲取了用戶id,無需再次對token解密 if (!empty($request->uid)) { return $next($request); } // 執(zhí)行到此代表請求頭中的 token 為空 throw new \Exception('請先登錄'); // 繼續(xù)執(zhí)行請求 return $next($request); } ``` Jwt 功能封裝類 JwtAuth.php ``` <?php declare(strict_types=1); namespace app\lib; use Firebase\JWT\JWT; use Firebase\JWT\Key; class JwtAuth { // 訪問密鑰 const KEY = 'ed6a18a9a'; // 簽發(fā)者 const ISS = 'liang'; // 接收者 const AUD = 'beautifulforever.com.cn'; // 加密算法 The signing algorithm const ALG = 'HS256'; /** * 對數據進行編碼 * * @param integer $uid */ public static function encode(int $uid) { $time = time(); $payload = [ "iss" => self::KEY, "aud" => self::KEY, "iat" => $time, "nbf" => $time, 'exp' => $time + 86400 * 30, 'data' => ['uid' => $uid], ]; $token = JWT::encode($payload, self::KEY, self::ALG); return $token; } /** * 對 token 進行編碼 * * @param string $token * @param integer $user_id */ public static function decode(string $token) { try { // 對 token 進行編碼 $decoded = JWT::decode($token, new Key(self::KEY, self::ALG)); // 檢測 token 附加數據中是否存在用戶id if (isset($decoded->data->uid) && $decoded->data->uid > 0) { $user_id = intval($decoded->data->uid); } else { throw new \Exception('token 中沒有用戶id'); } } catch (\Exception $e) { throw new \Exception($e->getMessage(), 201); } return $user_id; // 用戶id } } ``` #### 4. 使用說明 --- 通過上面代碼可以看到基礎控制器 Base.php 中定義了控制器中間件,需要登錄狀態(tài)校驗的控制器要繼承 Base 控制器即可 場景一: 控制器中的所有方法都要進行登錄狀態(tài)校驗,也就是只有登錄了才能訪問,則直接繼承即可 ``` use app\Request; class User extends Base { public function getProfile(Request $request) { $request->uid; // 用戶id } } ``` 場景二: 控制器中一部分方法必須登錄了才能訪問,一部分方法有沒有登錄都可以訪問 此時需要繼承 Base 控制器,并且重寫 $middleware 屬性 有沒有登錄都能訪問的方法使用 except 指定即可,此時 **$request->uid** 值為 0 或 用戶id ``` use app\Request; class User extends Base { protected $middleware = [ JwtMiddleware::class => [ // getLists 方法不執(zhí)行中間件 'except' => ['getLists'], ], ]; /** * 有沒有登錄都可以訪問 * * @param Request $request */ public function getLists(Request $request) { $request->uid; // 沒有登錄時值為 0 已登錄則值為用戶id } /** * 已登錄才能訪問 * * @param Request $request */ public function getProfile(Request $request) { $request->uid; // 用戶id } } ```