Web 安全 - 同事告訴我 JWT 是明文的---


一天 “小張” 接到一個需求 “一旦用戶登陸認證成功之後,後續的請求可以攜帶一個令牌,無需再次身份認證”。

這時 “小張” 諮詢了資深搬磚工程師 “小李”,憑藉多年的搬磚經驗,同事 “小李” 說到:HTTP 協議是無狀態的,在第一次登陸認證成功後,下一次請求時,服務器也不知道請求者的身份信息。通常有兩種實現方式:

“小張” 聽完後,連忙說到第二種聽着不錯哦,搜索了一些相關文章介紹之後就開始了愉快的代碼編寫。完成之後提交了代碼給同事 “小李” 做 code review,做爲資深搬磚工程師的 “小李”,一眼看出了問題:“怎麼能在 JWT 生成的 token 裏放用戶密碼呢!JWT 默認是明文的,不能存儲隱私信息”

“小張” 不解,反問道:怎麼會是明文呢,加密之後的數據我看了的,是一堆亂碼啊,下面是打印的 token 信息。

// jwt 簽名後生成的 token
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuW8oOS4iSIsInBhc3N3b3JkIjoxMjM0NTYsImlhdCI6MTY2MTg2OTQxMX0.3-60HUf_cKIo44hWUviNzqdUoUGngGQfrqffg0A6uqM"

“小李” 通過一段 Node.js 代碼展示瞭如何解密出 JWT 簽名後的 token 數據。

此時的 “小張” 陷入了沉思,頓時心裏產生了兩個疑問🤔️:

帶着這兩個疑問,下一步讓我們一塊瞭解下 JWT 的原理。

JWT 原理

JWT 全稱 JSON Web Token,是一種基於 JSON 的數據對象,通過技術手段將數據對象簽名爲一個可以被驗證和信任的令牌(Token)在客戶端和服務端之間進行安全的傳輸。

JWT Token 由三部分組成:header(頭信息)payload(消息體)signature(簽名),之間用 . 鏈接,構成如下所示:Header 部分由 JSON 對象 { typ, alg } 兩部分構成,使用 base64url(header) 算法轉爲字符串:

Payload 部分爲消息體,用來存儲需要傳輸的數據,同樣也是一個 JSON 對象使用base64url(payload) 算法轉爲字符串,JWT 提供了 7 個可選字段供選擇,也可以自定義字段:

**Signature 是對 Header、Payload 兩部分數據按照指定的算法做了一個簽名,防止數據被篡改。**需要指定一個 sceret,產生簽名的公式如下:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

生成簽名後,將 header.payload.signature 三部分鏈接在一起,形成一個令牌(token)返回給客戶端。

問題答疑

這就是 JWT 的原理,瞭解之後並沒有那麼神祕,回答上面的幾個問題。

簽名時使用了 secret,爲什麼是明文?

header、payload 部分是使用 base64 算法進行的編碼,並沒有被加密,自然也可以被解碼。但注意這裏的 base64 算法有點不一樣的地方在於,token 可能會被放在 url query 中傳輸,URL 裏面有三個特殊字符會被替換。下面是 JWT 中 base64url 的實現方式

// https://github1s.com/auth0/node-jws/blob/HEAD/lib/sign-stream.js#L9-L16
function base64url(string, encoding) {
  return Buffer
    .from(string, encoding)
    .toString('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');
}

還需要注意 payload 對象放置的內容越多,base64 之後的字符串就越大,同理簽名後的 token 也一樣。

數據會不會被篡改?

數據一旦被篡改,到服務端也會認證失敗的,服務端在生成簽名時有一個重要的參數是 secret,只要保證這個密鑰不被泄漏,就沒問題,就算篡改也是無效的。

Node.js 示例演示

在 Node.js 中使用 JWT 需要用到 jsonwebtoken 這個庫,API 很簡單,主要用到兩個方法:

const crypto = require('node:crypto');
const jwt = require('jsonwebtoken');

const secret = crypto.createHmac('sha256''abcdefg')
  .update('')
  .digest('hex');

const payload = {
  "username""張三",
  "password": 123456,
  iat: 1516239022
};
const token = jwt.sign(payload, secret)
const result = jwt.verify(token, secret)

總結

JWT 由服務端生成可以存儲在客戶端,對服務端來說是無狀態的,可擴展性好

上文我們也講了 JWT 中傳輸數據的 payload 默認是使用 base64 算法進行的編碼,看似一串亂碼,實則是沒有加密,因此不要將涉及到安全、用戶隱私的數據存放在 payload 中,如果要存放也請先自己進行加密

一旦 token 泄漏,任何人都可以使用,爲了減少 token 被盜用,儘可能的使用 HTTPS 協議傳輸,token 的過期時間也要設置的儘可能短

防止數據被篡改,服務端密鑰(secret)很重要,一定要保管好

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/eVaHk_YVxtmNs470p02I2g