[譯] 簡單 5 步,理解 JWT
這篇文章,將會解釋 JSON Web Tokens (JWT) 的基本原理以及為什麼使用它們。JWT 是確保你的應用程式可信任和安全的重要部分。JWT 允許以安全的方式來表示資訊,例如使用者資料。
為了解釋 JWT 的工作原理,我們從抽象的定義開始:
JSON Web Token (JWT) 是一個 JSON 物件, 在 ofollow,noindex">RFC 7519 中定義為一種用來表示雙方資訊集的安全方式。Token 由頭部 (header)、負載 (payload)、簽名 (signature) 組成。
簡單的說,JWT 只是形如下面格式的字串:
header.payload.signature
需要注意的是,雙引號字串也是合法的 JSON 物件
為了說明如何使用和為什麼使用 JWT,我們將用一個簡單的 3 實體示例(參見下圖)。例子裡的三個實體分別是使用者(user)、應用伺服器(application server)、認證伺服器(authentication server)。認證伺服器提供 JWT 給使用者,然後使用者可以安全的和應用通訊。
在這個例子中,使用者首先通過認證伺服器提供的登入系統(例如:使用者名稱和密碼,Facebook 登入,Google 登入,等等)登入認證伺服器。然後認證伺服器生成 JWT 並返回給使用者。當用戶嚮應用伺服器發起 API 請求時,會附帶上 JWT。在這一步中,應用伺服器會配置成去驗證傳入的 JWT 是否由認證伺服器生成的(驗證過程將在後面詳細解釋)。所以,當用戶攜帶 JWT 發起 API 請求時,應用可以通過 JWT 來校驗 API 請求是否來自認證的使用者。
現在,我們將更加深入的研究 JWT 本身及其構建和驗證的方式。
Step 1. 建立 HEADER
JWT 的頭部(header)模組包含了如何計算 JWT 簽名的資訊。header 是個 JSON 物件,格式如下:
{ "typ": "JWT", "alg": "HS256" }
這個 JSON 中,”type” 欄位的值指明這個物件是個 JWT,”alg” 欄位的值說明用什麼 hash 演算法來生成 JWT 簽名模組。例子中,我們使用的是 HMAC-SHA256 演算法 —— 帶有金鑰的 hash 演算法,來計算簽名(在 step 3 詳細說明)。
Step 2. 建立 PAYLOAD
JWT 的負載(payload)模組是儲存在其中的資料(這個資料也被稱為 JWT 的 “宣告”)。在例子中,認證伺服器生成 JWT,JWT 中儲存了使用者的資訊,具體的說是使用者 ID。
{ "userId": "b08f86af-35da-48f2-8fab-cef3904660bd" }
例子中,我們在 payload 中只儲存了一個宣告。你想要的話,也可以新增更多的宣告。JWT 的 payload 有幾種不同的標準宣告,例如:“iss” 表示發行人, “sub” 表示主題, 以及 “exp” 表示過期時間。這些欄位在建立 JWT 時非常有用,並且是可選的欄位。可以在 wikipedia page 上檢視詳細的 JWT 標準 欄位列表。
要記住,資料的大小會影響整個 JWT 的大小,整個一般不是問題,但是太大的 JWT 可能會對效能有負面影響並導致延遲。
Step 3. 建立 SIGNATURE
簽名(signature)是有下面的虛擬碼計算得到的:
// signature algorithm data = base64urlEncode( header ) + “.” + base64urlEncode( payload ) hashedData = hash( data, secret ) signature = base64urlEncode( hashedData )
這個演算法做的是:對步驟 1 和 2 生成的 header 和 payload 進行 base64url encodes 編碼,然後把編碼生成的字串中間通過句號(.)連線起來。在虛擬碼中,把連線的字串賦值給 data
。通過 JWT header 中定義的 hash 演算法,加上金鑰對 data
字串 hash 處理。把 hash 後的資料賦值給 hashedData
。這個 hash 後的資料通過 base64url 編碼生成 JWT 簽名(signature)。
我們的例子中,header 和 payload 被 base64url 編碼成:
// header eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 // payload eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ
然後,將編譯後的 header 和 payload 進行拼接,使用指定的簽名演算法和金鑰對它計算,得到簽名需要的 hash 後的資料。在我們例子中,也就是使用 HS256 演算法,金鑰為字串 secret
,對字串 data
計算得到字串 hashedData
。然後,通過 base64url 對字串 hashedData
編碼得到下面的 JWT 簽名(signature):
// signature -xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
Step 4. 將 JWT 三個模組合併
現在,我們已經建立了所有的三個模組,可以生成 JWT 了。記住 JWT 的結構 header.payload.signature
,我們簡單的使用句點(.)作為分隔來合併他們。我們使用 base64url 編碼後的 header 和 payload,以及在 Step 3 得到的 signature:
// JWT Token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
你可以使用 jwt.io 去建立你的 JWT。
回到我們的例子,現在認證伺服器可以把這個 JWT 傳送給使用者。
JWT 是如何保護資料的
重要的是要理解,使用 JWT 的目的不是以任何方式隱藏或者模糊資料。使用 JWT 的目的是為了證明發送的資料是由可信的源建立的。
正如前面的步驟所示,JWT 裡的資料被編碼、簽名,但是沒有加密。編碼資料的目的是為了轉化資料的結構。簽名資料是可以讓資料接收者校驗資料來源的可靠性。所以編碼和簽名資料並沒有保護資料。而另一方面,加密的主要目的是保護資料和防止未授權訪問。關於編碼和加密的不同之處的詳細解釋,以及更多關於 hash 的原理,可以看 這篇文章 。
由於 JWT 只是簽名和編碼資料,沒有加密,所有 JWT 不保證敏感資料的任何安全性。
Step 5. 校驗 JWT
在我們的簡單 3 實體例項中,我們使用的 JWT 是由 HS256 演算法簽名 —— 這個演算法的金鑰只有認證伺服器和應用伺服器知道。當應用伺服器設定他的認證過程時,應用伺服器從認證伺服器那接收金鑰。由於應用知道金鑰,當用戶嚮應用發起攜帶 JWT 的 API 請求時,應用可以執行和 Step 3 一樣的簽名演算法。然後應用可以校驗它自己 hash 操作生成的簽名是否和 JWT 中的簽名匹配(即它匹配認證伺服器建立的 JWT 簽名)。如果簽名匹配,意味著 JWT 是合法的,標明 API 請求是來自可靠的來源。否則,如果簽名不匹配,意味著接收的 JWT 是無效的,這也可能說明應用遭受潛在的攻擊。所以,通過校驗 JWT,應用在和使用者之間建立了信任。
總結
我們瞭解了 JWT 是什麼,如何建立和校驗它,以及如何用它來建立應用和使用者的信任。這是理解 JWT 的基本原理和它的作用的起點。JWT 只是確保應用中信任和安全性的難題之一。
需要指出的是,本文所述的 JWT 認證設定使用的是對稱金鑰演算法(HS256)。你也可以用類似的方式來設定你的 JWT 認證,只是使用非對稱演算法(例如 RS256)—— 認證伺服器有一個金鑰,應用有一個公鑰。關於使用對稱和非對稱演算法的不同之處的詳細分析,可以檢視 這個 Stack Overflow 上的問題 。
同時要說明的是,JWT 必須通過 HTTPS 連線(不是 HTTP)來發送。使用 HTTPS 可以防止未授權使用者竊取傳送的 JWT,從而無法攔截服務端和使用者之間的通訊。
還有就是,給你的 JWT payload 設定有效期 —— 特別是很短的有效期,這個很重要,從而如果舊的 JWT 被洩漏,它可能已經無效並無法在使用了。