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