一篇文章告訴你JWT的實現原理
- 2020 年 4 月 8 日
- 筆記
編輯:業餘草 來源:https://www.xttblog.com/?p=4940
寫篇文章不容易,昨天晚上深夜寫完忘記保存了。聯想一下那個畫面,真的是淚目啊!
哎,過去的就讓他過去吧,今天我們繼續說 JWT。
在使用 JWT 的時候,有沒有想過,為什麼我們需要 JWT?以及它的工作原理是什麼?
我們就來對比,傳統的 session 和 JWT 的區別
我們以一個用戶,獲取用戶資料的例子
傳統的 session 流程
- 瀏覽器發起請求登陸
- 服務端驗證身份,生成身份驗證資訊,存儲在服務端,並且告訴瀏覽器寫入 Cookie
- 瀏覽器發起請求獲取用戶資料,此時 Cookie 內容也跟隨這發送到伺服器
- 伺服器發現 Cookie 中有身份資訊,驗明正身
- 伺服器返回該用戶的用戶資料
JWT 流程
- 瀏覽器發起請求登陸
- 服務端驗證身份,根據演算法,將用戶標識符打包生成 token, 並且返回給瀏覽器
- 瀏覽器發起請求獲取用戶資料,把剛剛拿到的 token 一起發送給伺服器
- 伺服器發現數據中有 token,驗明正身
- 伺服器返回該用戶的用戶資料
你發現了嗎?好些並沒有什麼區別,除了 session 需要服務端存儲一份,而 JWT 不需要
但實際上區別大了去了
- session 存儲在服務端佔用伺服器資源,而 JWT 存儲在客戶端
- session 存儲在 Cookie 中,存在偽造跨站請求偽造攻擊的風險
- session 只存在一台伺服器上,那麼下次請求就必須請求這台伺服器,不利於分散式應用
- 存儲在客戶端的 JWT 比存儲在服務端的 session 更具有擴展性
- …
對比完了 session 和 JWT 的區別,下面我們來看看那它的實現原理
JWT 工作原理

首先 JWT 長這個樣
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6IjEyNy4wLjAuMSIsInV1aWQiOiJmZjEyMTJmNS1kOGQxLTQ0OTYtYmY0MS1kMmRkYTczZGUxOWEiLCJpYXQiOjE1Mjc1MjMwMTd9.1C01cpOf1N3M9YAYvQjfcLbDGHZ4iPVncCGIoG-lpO0jHOIAZHtSMDvK1nzArLpGK5syQSwExsZJz2FJsd2W2TUiHQYtzmQTU8OBXX6mfSZRlkts675W5WhIiOEwz69GFSD0AKXZifCRgIpKLC0n273MRMr0wJnuBi9ScfJ7YjSiqCr7qyQ5iXeOdS3ObT3wdjjk-Wu9wbWM7R25TFb-7PEZY7re8jmczPCVcNbOYegedu73T4d30kRn2jKufTGntD5hR6T9AQsgAMwVR1edEFflWb772TmrHI7WZOAivsBCN9sr4YiyYMvE8lczmBsgsunugGiHA3DGxB2ORbjIC8NPm8FI25zGOh9JIM2rjFFTIm9GiuKtC8Ck8N3-eWi9u1NgBxwLdgN5JyCORnIOlciQEsScg-3SdCTM5LHj6CeqQNwJxT4-oENzqLSTDJbP-SOj9nnx8HnJ5wh3n64rAvtc89CeTk7PhWFjksHDifngN-cnaszl5lqoF1enz5i9FYYELSjM-G7jns2SyY1MQeLRjuEDriPZtFaGmTW-RLH3gJfQXtbdpEo0nHBqXEohwoNFLKo4BNrEwshpyA7vkBpCQC0QALKyC1_L1Q5qduR6dDcqRozAo2VqJXmAKN0rvhLuIEHZkicOZY0Ds4So4GCcleqvFcxm1HQ
眼睛看仔細一些,你會發現 JWT 裡面有兩個.
數據格式是這樣的 header.payload.signature
我們逐個逐個部分去分析,這個部分到底是幹嘛的,有什麼用
Header
JWT 的 header 中承載了兩部分資訊
{ "alg": "RS256", "typ": "JWT" }
- alg: 聲明加密的演算法
- typ: 聲明類型
對這個頭部資訊進行 base64,即可得到 header 部分
const headerBuff = Buffer.from( JSON.stringify({ alg: "RS256", typ: "JWT" }) ); const header = headerBuff.toString("base64"); console.log(header); // eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
Payload
payload 是主體部分,意為載體,承載著有效的 JWT 數據包,它包含三個部分
- 標準聲明
- 公共聲明
- 私有聲明
標準聲明的欄位
interface Stantar { iss?: string; // JWT的簽發者 sub?: string; // JWT所面向的用戶 aud?: string; // 接收JWT的一方 exp?: number; // JWT的過期時間 nbf?: number; // 在xxx日期之間,該JWT都是可用的 iat?: number; // 該JWT簽發的時間 jti?: number; //JWT的唯一身份標識 }
標準中建議使用這些欄位,但不強制。
公共聲明的欄位
interface Public { [key: string]: any; }
公共聲明欄位可以添加任意資訊,但是因為可以被解密出來,所以不要存放敏感資訊。
私有聲明的欄位
interface Private { [key: string]: any; }
私有聲明是 JWT 提供者添加的欄位,一樣可以被解密,所以也不能存放敏感資訊。
上面的 JWT 的 payload 結構是這樣的
{ "ip": "127.0.0.1", "uuid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a", "iat": 1527523017 }
同樣是通過 base64 加密生成第二部分的 payload
const payloadBuffer = Buffer.from( JSON.stringify({ ip: "127.0.0.1", uuid: "ff1212f5-d8d1-4496-bf41-d2dda73de19a", iat: 1527523017 }) ); const payload = payloadBuffer.toString("base64"); console.log(payload); // eyJpcCI6IjEyNy4wLjAuMSIsInV1aWQiOiJmZjEyMTJmNS1kOGQxLTQ0OTYtYmY0MS1kMmRkYTczZGUxOWEiLCJpYXQiOjE1Mjc1MjMwMTd9
Signature
signature 是簽證資訊,該簽證資訊是通過header和payload,加上secret,通過演算法加密生成。
公式 signature = 加密演算法(header + "." + payload, 密鑰);
上面的 header 中,我們已經定義了加密演算法使用 RS256,也已經實現了生成header和payload,下面我們來生成 signature
const crypto = require("crypto"); const sign = crypto.createSign("SHA256"); const secret = `私鑰,太長我就不貼出來了`; sign.write(header + "." + payload); sign.end(); const signature = sign .sign(secret, "base64") // 在JWT庫中,已經把這些字元過濾掉了 .replace(/=/g, "") .replace(/+/g, "-") .replace(///g, "_"); console.log(signature);
到此,已經實現了如何生成一個 JWT 的 token.
它是如何做身份驗證的?
首先,JWT 的 Token 相當是明文,是可以解密的,任何存在 payload 的東西,都沒有秘密可言,所以隱私數據不能簽發 token。
而服務端,拿到 token 後解密,即可知道用戶資訊,例如本例中的uuid
有了 uuid,那麼你就知道這個用戶是誰,是否有許可權進行下一步的操作。
Token 的過期時間怎麼確定?
payload 中有個標準欄位 exp,明確表示了這個 token 的過期時間.
服務端可以拿這個時間與伺服器時間作對比,過期則拒絕訪問。
如何防止 Token 被串改?
此時 signature欄位就是關鍵了,能被解密出明文的,只有header和payload
假如黑客/中間人串改了payload,那麼伺服器可以通過signature去驗證是否被篡改過。
在服務端在執行一次 signature = 加密演算法(header + "." + payload, 密鑰);, 然後對比 signature 是否一致,如果一致則說明沒有被篡改。
所以為什麼說伺服器的密鑰不能被泄漏。
如果泄漏,將存在以下風險:
- 客戶端可以自行簽發 token
- 黑客/中間人可以肆意篡改 token
安全性相關
如果加強 JWT 的安全性?
根據我的使用,總結以下幾點:
- 縮短 token 有效時間
- 使用安全係數高的加密演算法
- token 不要放在 Cookie 中,有 CSRF 風險
- 使用 HTTPS 加密協議
- 對標準欄位 iss、sub、aud、nbf、exp 進行校驗
- 使用成熟的開源庫,不要手賤造輪子
- 特殊場景下可以把用戶的 UA、IP 放進 payload 進行校驗(不推薦)
參考文獻
- RFC 7519
以上純屬個人愚見,大家笑笑就好. 本來 JWT 已經是爛大街的東西,奈何還是有些所謂的「架構師」在不了解的情況下,誤用,誤導廣大群眾!