JWT的解析和加密。分散式登入 SSO登入 OAuth2 SpringSecurity
阿新 • • 發佈:2018-11-22
一篇文章告訴你JWT的實現原理
傳統的 session 流程
- 瀏覽器發起請求登陸
- 服務端驗證身份,生成身份驗證資訊,儲存在服務端,並且告訴瀏覽器寫入 Cookie
- 瀏覽器發起請求獲取使用者資料,此時 Cookie 內容也跟隨這傳送到伺服器
- 伺服器發現 Cookie 中有身份資訊,驗明正身
- 伺服器返回該使用者的使用者資料
JWT 流程
- 瀏覽器發起請求登陸
- 服務端驗證身份,根據演算法,將使用者識別符號打包生成 token, 並且返回給瀏覽器
- 瀏覽器發起請求獲取使用者資料,把剛剛拿到的 token 一起傳送給伺服器
- 伺服器發現數據中有 token,驗明正身
- 伺服器返回該使用者的使用者資料
- session 儲存在服務端佔用伺服器資源,而 JWT 儲存在客戶端
- session 儲存在 Cookie 中,存在偽造跨站請求偽造攻擊的風險
- session 只存在一臺伺服器上,那麼下次請求就必須請求這臺伺服器,不利於分散式應用
- 儲存在客戶端的 JWT 比儲存在服務端的 session 更具有擴充套件性
- …
JWT 工作原理
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6IjEyNy4wLjAuMSIsInV1aWQiOiJmZjEyMTJmNS1kOGQxLTQ0OTYtYmY0MS1kMmRkYTczZGUxOWEiLCJpYXQiOjE1Mjc1MjMwMTd9.1C01cpOf1N3M9YAYvQjfcLbDGHZ4iPVncCGIoG-lpO0jHOIA_ZHtSMDvK1nzArLpGK5syQSwExsZJz2FJsd2W2TUiHQYtzmQTU8OBXX6mfSZRlkts675W5_WhIiOEwz69GFSD0AKXZifCRgIpKLC0n273MRMr0wJnuBi9ScfJ7YjSiqCr7qyQ5iXeOdS3ObT3wdjjk-Wu9wbWM7R25TFb-7PEZY7r_e8jmczPCVcNbOYegedu73T4d30kRn2jKufTGntD5hR6T9AQsgAMwVR1edEFflWb772TmrHI7WZOAivsBCN9sr4YiyYMvE8lcz_mBsgsunugGiHA3DGxB2ORbjIC8NPm8FI25zGOh9JIM2r_jFFTIm9GiuKtC8Ck8N3-eWi9u1NgBxwLdgN5JyCORnIOlciQEsScg-3SdCTM5LH_j6CeqQNwJxT4-oENzqLSTDJbP-SOj9nnx8HnJ5wh3n64rAvtc89CeTk7PhWFjksHDifngN-cnaszl5lqoF1enz5i9FYYELSjM-G7jns2SyY1MQeLRjuEDriPZtFaGmTW-RLH3gJfQXtbdpEo0_nHBqXEohwoN_FLKo4BNrEwshpyA7vkBpCQC0QALKyC1_L1Q5qduR6dDcqRozAo2VqJXmAKN0rvhLuIEHZkicOZY0Ds4So4GCcleqvFcxm1HQ
Header
{
"alg": "RS256",
"typ": "JWT"
}
- alg: 宣告加密的演算法
- typ: 宣告型別
const headerBuff = Buffer.from( JSON.stringify({ alg: "RS256", typ: "JWT" }) ); const header = headerBuff.toString("base64"); console.log(header); // eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
Payload
- 標準宣告
- 公共宣告
- 私有宣告
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;
}
{
"ip": "127.0.0.1",
"uuid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
"iat": 1527523017
}
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
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);
它是如何做身份驗證的?
Token 的過期時間怎麼確定?
如何防止 Token 被串改?
- 客戶端可以自行簽發 token
- 黑客/中間人可以肆意篡改 token
安全性相關
- 縮短 token 有效時間
- 使用安全係數高的加密演算法
- token 不要放在 Cookie 中,有 CSRF 風險
- 使用 HTTPS 加密協議
- 對標準欄位 iss、sub、aud、nbf、exp 進行校驗
- 使用成熟的開源庫,不要手賤造輪子
- 特殊場景下可以把使用者的 UA、IP 放進 payload 進行校驗(不推薦)