1. 程式人生 > >JWT的解析和加密。分散式登入 SSO登入 OAuth2 SpringSecurity

JWT的解析和加密。分散式登入 SSO登入 OAuth2 SpringSecurity

一篇文章告訴你JWT的實現原理

 

 

 

傳統的 session 流程

  1. 瀏覽器發起請求登陸
  2. 服務端驗證身份,生成身份驗證資訊,儲存在服務端,並且告訴瀏覽器寫入 Cookie
  3. 瀏覽器發起請求獲取使用者資料,此時 Cookie 內容也跟隨這傳送到伺服器
  4. 伺服器發現 Cookie 中有身份資訊,驗明正身
  5. 伺服器返回該使用者的使用者資料

JWT 流程

  1. 瀏覽器發起請求登陸
  2. 服務端驗證身份,根據演算法,將使用者識別符號打包生成 token, 並且返回給瀏覽器
  3. 瀏覽器發起請求獲取使用者資料,把剛剛拿到的 token 一起傳送給伺服器
  4. 伺服器發現數據中有 token,驗明正身
  5. 伺服器返回該使用者的使用者資料

 

 

  1. session 儲存在服務端佔用伺服器資源,而 JWT 儲存在客戶端
  2. session 儲存在 Cookie 中,存在偽造跨站請求偽造攻擊的風險
  3. session 只存在一臺伺服器上,那麼下次請求就必須請求這臺伺服器,不利於分散式應用
  4. 儲存在客戶端的 JWT 比儲存在服務端的 session 更具有擴充套件性

 

JWT 工作原理

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

安全性相關

 

 

  1. 縮短 token 有效時間
  2. 使用安全係數高的加密演算法
  3. token 不要放在 Cookie 中,有 CSRF 風險
  4. 使用 HTTPS 加密協議
  5. 對標準欄位 iss、sub、aud、nbf、exp 進行校驗
  6. 使用成熟的開源庫,不要手賤造輪子
  7. 特殊場景下可以把使用者的 UA、IP 放進 payload 進行校驗(不推薦)