JWT介紹:
全稱JSON Web Token,基於JSON的開放標準((RFC 7519) ,以token的方式代替傳統的Cookie-Session模式,用於各伺服器、客戶端傳遞訊息簽名驗證。
JWT優點:
1:伺服端不需要儲存傳統會話訊息,沒有跨域傳輸問題,減小伺服器開銷。
2:jwt構成簡單,佔用很少的位元組,便於傳輸。
3:json格式通用,不同語言之間都可以使用。
JWT組成
jwt由三部分組成:
頭部(header)
載荷(payload) 包含一些定義訊息和自定義訊息
簽證(signature)
1:具體構成:header
1 2 3 4 | {<!-- --> "typ": "JWT", //宣告型別為jwt "alg": "HS256" //宣告簽名演算法為SHA256 } |
2:具體構成: 載荷(payload)
1 2 3 4 5 6 7 8 9 10 11 | {<!-- --> "iss": "http://www.helloweba.net", "aud": "http://www.helloweba.net", "iat": 1525317601, "nbf": 1525318201, "exp": 1525318201, "data": {<!-- --> "userid": 1, "username": "李小龍" } } |
載荷包括兩部分:標準宣告和其他宣告。
標準宣告:JWT標準規定的宣告,但不是必須填寫的;
標準宣告欄位:
接收該JWT的一方
1 2 3 4 5 6 7 8 9 10 11 12 13 | iss: jwt簽發者 sub: jwt所面向的使用者 aud: 接收jwt的一方 exp: jwt的過期時間,過期時間必須要大於簽發時間 nbf: 定義在什麼時間之前,某個時間點後才能訪問 iat: jwt的簽發時間 jti: jwt的唯一身份標識,主要用來作為一次性token。 |
其他宣告:
自己定義的欄位,
最終是這樣的:

三部分分別以逗號隔開
四:使用:PHP有很多jwt包,包括例如:lcobucci/jwt,我這裡使用firebase。可以從 git地址 下載
例項應用
1 安裝
2 伺服端簽發Token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php use FirebaseJWTJWT; //匯入JWT class MainController extends Controller {<!-- --> //簽發Token public function lssue() {<!-- --> $key = '344'; //key $time = time(); //當前時間 $token = [ 'iss' => 'http://www.helloweba.net', //簽發者 可選 'aud' => 'http://www.helloweba.net', //接收該JWT的一方,可選 'iat' => $time, //簽發時間 'nbf' => $time , //(Not Before):某個時間點後才能訪問,例如設定time+30,表示當前時間30秒後才能使用 'exp' => $time+7200, //過期時間,這裡設定2個小時 'data' => [ //自定義訊息,不要定義敏感訊息 'userid' => 1, 'username' => '李小龍' ] ]; echo JWT::encode($token, $key); //輸出Token } } |
3 解析Token,為了演示,這裡直接寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?php use FirebaseJWTJWT; //匯入JWT class MainController extends Controller {<!-- --> public function verification() {<!-- --> $key = '344'; //key要和簽發的時候一樣 $jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC93d3cuaGVsbG93ZWJhLm5ldCIsImF1ZCI6Imh0dHA6XC9cL3d3dy5oZWxsb3dlYmEubmV0IiwiaWF0IjoxNTI1MzQwMzE3LCJuYmYiOjE1MjUzNDAzMTcsImV4cCI6MTUyNTM0NzUxNywiZGF0YSI6eyJ1c2VyaWQiOjEsInVzZXJuYW1lIjoiXHU2NzRlXHU1YzBmXHU5Zjk5In19.Ukd7trwYMoQmahOAtvNynSA511mseA2ihejoZs7dxt0"; //簽發的Token try {<!-- --> JWT::$leeway = 60;//當前時間減去60,把時間留點餘地 $decoded = JWT::decode($jwt, $key, ['HS256']); //HS256方式,這裡要和簽發的時候對應 $arr = (array)$decoded; print_r($arr); } catch(FirebaseJWTSignatureInvalidException $e) {<!-- --> //簽名不正確 echo $e->getMessage(); }catch(FirebaseJWTBeforeValidException $e) {<!-- --> // 簽名在某個時間點之後才能用 echo $e->getMessage(); }catch(FirebaseJWTExpiredException $e) {<!-- --> // token過期 echo $e->getMessage(); }catch(Exception $e) {<!-- --> //其他錯誤 echo $e->getMessage(); } //Firebase定義了多個 throw new,我們可以捕獲多個catch來定義問題,catch加入自己的業務,例如token過期可以用當前Token重新整理一個新Token } } |
在src目錄下的JWT.php中的方法 decode中,可以看到作者是透過多個 自定義 throw new 丟擲異常的

所以我們可以根據不同的throw new設定多個catch來捕獲。
輸出的資料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Array ( [iss] => http://www.helloweba.net [aud] => http://www.helloweba.net [iat] => 1525340317 [nbf] => 1525340317 [exp] => 1525347517 [data] => stdClass Object ( [userid] => 1 [username] => 李小龍 ) ) |
範例分析
1:方案:客戶端透過使用者名稱密碼登入以後,伺服端回傳給客戶端兩個token:access_token和refresh_token。
access_token:請求介面的token
refresh_token:重新整理access_token
舉個範例:例如access_token設定2個小時過期,refresh_token設定7天過期,2小時候後,access_token過期,但是refresh_token還在7天以內,那麼客戶端透過refresh_token來伺服端重新整理,伺服端重新生成一個access_token;如果refresh_token也超過了7天,那麼客戶端需要重新登入取得access_token和refresh_token。
為了區分兩個token,我們在載荷(payload)加一個欄位 scopes :作用域。
access_token中設定:scopes:role_access
refresh_token中設定:scopes:role_refresh
有些人是用一個token,要麼設定很長時間過期;要麼設定幾個小時過期,如果過期,用過期的token去換取新的token,這種其實是有問題的,有些人說token有兩個時間,一個是過期時間,一個是重新整理時間,只要在重新整理時間內就可以換取新token。假如別人拿到了你這token,如果也在過期時間之內,不是同樣可以重新整理token?除非兩個token都拿到,相對來說第二種更安全。
直接上程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php use FirebaseJWTJWT; //匯入JWT class MainController extends Controller {<!-- --> public function authorizations() {<!-- --> $key = 'ffdsfsd@4_45'; //key $time = time(); //當前時間 //公用訊息 $token = [ 'iss' => 'http://www.helloweba.net', //簽發者 可選 'iat' => $time, //簽發時間 'data' => [ //自定義訊息,不要定義敏感訊息 'userid' => 1, ] ]; $access_token = $token; // access_token $access_token['scopes'] = 'role_access'; //token標識,請求介面的token $access_token['exp'] = $time+7200; //access_token過期時間,這裡設定2個小時 $refresh_token = $token; //refresh_token $refresh_token['scopes'] = 'role_refresh'; //token標識,重新整理access_token $refresh_token['exp'] = $time+(86400 * 30); //refresh_token過期時間,這裡設定30天 $jsonList = [ 'access_token'=>JWT::encode($access_token,$key), 'refresh_token'=>JWT::encode($refresh_token,$key), 'token_type'=>'bearer' //token_type:表示令牌型別,該值大小寫不敏感,這裡用bearer ]; Header("HTTP/1.1 201 Created"); echo json_encode($jsonList); //回傳給客戶端token訊息 } } |
看起來輸出是這樣的:
1 2 3 4 5 | { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC93d3cuaGVsbG93ZWJhLm5ldCIsImlhdCI6MTUyNTQxNzg5NywiZGF0YSI6eyJ1c2VyaWQiOjF9LCJzY29wZXMiOiJyb2xlX2FjY2VzcyIsImV4cCI6MTUyNTQyNTA5N30.Nxp1yutwt8Fxj5XEzes4j-X4tCBQwE0htEV3Msm2D8s", "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC93d3cuaGVsbG93ZWJhLm5ldCIsImlhdCI6MTUyNTQxNzg5NywiZGF0YSI6eyJ1c2VyaWQiOjF9LCJzY29wZXMiOiJyb2xlX3JlZnJlc2giLCJleHAiOjE1MjgwMDk4OTd9.YY8Lid3nk3bnV-ZnAYneKJxGiaD73waqTpC3bHz3wsY", "token_type": "bearer" } |
HTTP標頭部回應是這樣的:兩個token客戶端儲存,一個用來取介面,一個用來重新整理介面。

客戶端請求的時候在HTTP標頭部攜帶 Authorization: bearer token,注意bearer後面有個空白,我們用PHP模擬一下。

請求訊息是這樣的,PHP取得Authorization訊息判斷。
這個只是一種思路,大家可以按照自己的思路去弄,這種思路伺服端不用儲存token,但如果涉及到token的登出就必須儲存,例如token沒過期,但是要登出token,這時候可以上redis。