[TOC] #### 1. JWT介紹 --- 本文是在 TP6.0 使用 JWT 的示例 JWT全稱(chēng): JSON Web Token,以 token 的方式代替?zhèn)鹘y(tǒng)的 cookie、session 模式,用于各服務(wù)器、客戶(hù)端傳遞信息及簽名驗(yàn)證 #### 2. 新增自定義函數(shù) `fault()` --- 在 **app/common.php** 中新增以下函數(shù),用于拋出異常 ```php /** * 拋出異常錯(cuò)誤 * * @param string $msg * @param integer $code */ function fault(string $msg = "", $code = 201) { throw new \Exception($msg, $code); } ``` #### 3. 新增配置文件 `jwt.php` --- 在全局配置目錄 config 目錄下新建 jwt.php 文件,文件內(nèi)容如下 ``` <?php // +---------------------------------------------------------------------- // | JWT (Json Web Token) 配置 // +---------------------------------------------------------------------- // | Author: liang 23426945@qq.com // +---------------------------------------------------------------------- return [ 'iss' => 'liang', // 簽發(fā)者 'aud' => 'chen', // 接收者 'key' => 'yang', // 訪問(wèn)密鑰 'prefix' => 'jwt_', // 緩存前綴 'exp' => 864000, // 過(guò)期時(shí)間,864000秒=10天 'single_sign_on' => false, // 單點(diǎn)登錄 true 開(kāi)啟 false 關(guān)閉 ]; ``` #### 4. JWT 功能封裝類(lèi) --- 安裝擴(kuò)展包 ``` composer require firebase/php-jwt:'5.*' ``` ``` <?php // +---------------------------------------------------------------------- // | JWT (Json Web Token) 功能封裝類(lèi) // +---------------------------------------------------------------------- // | Author: liang 23426945@qq.com // +---------------------------------------------------------------------- declare(strict_types=1); use Firebase\JWT\JWT; class JwtAuth { // +------------------------------------------------------------------ // | 初始化配置 // +------------------------------------------------------------------ /** * 初始化配置 */ public function __construct() { $this->iss = config('jwt.iss'); //簽發(fā)者 可選 $this->aud = config('jwt.aud'); //接收該JWT的一方,可選 $this->exp = config('jwt.exp'); //過(guò)期時(shí)間,864000秒 = 10天 $this->key = config('jwt.key'); //訪問(wèn)秘鑰 $this->prefix = config('jwt.prefix'); //緩存前綴 } // +------------------------------------------------------------------ // | 創(chuàng)建、解析 token // +------------------------------------------------------------------ /** * 創(chuàng)建token * * @param array $data * @return string token */ public function encode(array $data) { $time = time(); //當(dāng)前時(shí)間 $token = [ 'iss' => $this->iss, //簽發(fā)者 可選 'aud' => $this->aud, //接收該JWT的一方,可選 'iat' => $time, //簽發(fā)時(shí)間 'nbf' => $time, //(Not Before):某個(gè)時(shí)間點(diǎn)后才能訪問(wèn),比如設(shè)置time+30,表示當(dāng)前時(shí)間30秒后才能使用 'exp' => $time + $this->exp, //過(guò)期時(shí)間 'data' => $data, //附加數(shù)據(jù) ]; $token = JWT::encode($token, $this->key); // 創(chuàng)建token $this->cache($data['uid'], $token); // 將token存入緩存 return $token; // 返回token } /** * 解析token * * @param string $token 前端請(qǐng)求攜帶的token */ public function decode(string $token) { try { JWT::$leeway = 0; //當(dāng)前時(shí)間減去60,把時(shí)間留點(diǎn)余地 return JWT::decode($token, $this->key, ['HS256']); //HS256方式,這里要和簽發(fā)的時(shí)候?qū)?yīng) } catch (\Firebase\JWT\SignatureInvalidException $e) { //簽名不正確 fault('簽名不正確'); } catch (\Firebase\JWT\BeforeValidException $e) { // 簽名在某個(gè)時(shí)間點(diǎn)之后才能用 fault('登錄未生效'); } catch (\Firebase\JWT\ExpiredException $e) { // token過(guò)期 fault('登錄過(guò)期'); } catch (\Exception $e) { //其他錯(cuò)誤 fault($e->getMessage()); } } // +------------------------------------------------------------------ // | 檢測(cè)token // +------------------------------------------------------------------ /** * 將用戶(hù)token存入緩存,用于單點(diǎn)登錄校驗(yàn) * * @param int $id 用戶(hù)id * @param string $token 服務(wù)器端生成的token */ private function cache(int $uid, string $token) { // 緩存token cache($this->prefix . $uid, $token); } /** * 檢測(cè)token是否已過(guò)期(單點(diǎn)登錄) * * @param int $id 用戶(hù)id * @param string $token 前端請(qǐng)求攜帶的token * @return boolean true token 有效 false 已過(guò)期 */ public function checkToken(int $id, string $token) { // 判斷是否開(kāi)啟單點(diǎn)登錄校驗(yàn) true 開(kāi)啟 false 關(guān)閉 if (config('jwt.single_sign_on')) { // 獲取緩存中該用戶(hù)的token $cacheToken = cache($this->prefix . $id); // true 有效 false 已過(guò)期 return $token === $cacheToken; } else { return true; // 沒(méi)有開(kāi)啟單點(diǎn)登錄直接返回true } } } ``` #### 5. JWT 鑒權(quán)控制器 --- 創(chuàng)建鑒權(quán)控制器,所有需要進(jìn)行 token 校驗(yàn)的控制器只需要繼承該控制器即可 ``` php think make:controller api@Auth --plain ``` 鑒權(quán)控制器文件內(nèi)容如下: ``` <?php // +---------------------------------------------------------------------- // | JWT (Json Web Token) // +---------------------------------------------------------------------- // | Author: liang 23426945@qq.com // +---------------------------------------------------------------------- declare(strict_types=1); namespace app\api\controller; use JwtAuth; use app\BaseController; /** * JWT 鑒權(quán)控制器 */ class Auth extends BaseController { /** * 校驗(yàn)token是否有效 */ protected function initialize() { // 接收請(qǐng)求頭中的Token $token = request()->header('token'); // 初步校驗(yàn)token empty($token) && fault('token不能為空'); // 解析token,返回生成token時(shí)的附加數(shù)據(jù) $this->jwt = app(JwtAuth::class)->decode($token)->data; // 獲取附加數(shù)據(jù)中的用戶(hù)id并轉(zhuǎn)為整型 $this->uid = (int) $this->jwt->uid; // 攜帶的token和緩存中的token進(jìn)行比對(duì)(單點(diǎn)登錄校驗(yàn)) if (!app(JwtAuth::class)->checkToken($this->uid, $token)) { fault('登錄狀態(tài)已過(guò)期', 401); } } } ```