1. 程式人生 > >一篇文章讀懂JWT

一篇文章讀懂JWT

其實在之前已經寫過有關JWT的文章了,最近的專案中用到驗證Token的地方還挺多的,那麼趁著熱乎勁兒,讓我們深入的學習一下下這個Token到底是個什麼東西。

什麼是JWT?

Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準((RFC 7519).該token被設計為緊湊且安全的,特別適用於分散式站點的單點登入(SSO)場景。JWT的宣告一般被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊,該token也可直接被用於認證,也可被加密。

說起JWT,我們應該來談一談基於token的認證和傳統的session認證的區別。

傳統的session認證

我們知道,http協議本身是一種無狀態的協議,而這就意味著如果使用者向我們的應用提供了使用者名稱和密碼來進行使用者認證,那麼下一次請求時,使用者還要再一次進行使用者認證才行,因為根據http協議,我們並不能知道是哪個使用者發出的請求,所以為了讓我們的應用能識別是哪個使用者發出的請求,我們只能在伺服器儲存一份使用者登入的資訊,這份登入資訊會在響應時傳遞給瀏覽器,告訴其儲存為cookie,以便下次請求時傳送給我們的應用,這樣我們的應用就能識別請求來自哪個使用者了,這就是傳統的基於session認證。

但是這種基於session的認證使應用本身很難得到擴充套件,隨著不同客戶端使用者的增加,獨立的伺服器已無法承載更多的使用者,而這時候基於session認證應用的問題就會暴露出來.

基於session認證所顯露的問題

Session: 每個使用者經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便使用者下次請求的鑑別,通常而言session都是儲存在記憶體中,而隨著認證使用者的增多,服務端的開銷會明顯增大。

擴充套件性: 使用者認證之後,服務端做認證記錄,如果認證的記錄被儲存在記憶體中的話,這意味著使用者下次請求還必須要請求在這臺伺服器上,這樣才能拿到授權的資源,這樣在分散式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴充套件能力。

CSRF: 因為是基於cookie來進行使用者識別的, cookie如果被截獲,使用者就會很容易受到跨站請求偽造的攻擊。

基於token的鑑權機制

基於token的鑑權機制類似於http協議也是無狀態的,它不需要在服務端去保留使用者的認證資訊或者會話資訊。這就意味著基於token認證機制的應用不需要去考慮使用者在哪一臺伺服器登入了,這就為應用的擴充套件提供了便利。

流程上是這樣的:

  • 使用者使用使用者名稱密碼來請求伺服器
  • 伺服器進行驗證使用者的資訊
  • 伺服器通過驗證傳送給使用者一個token
  • 客戶端儲存token,並在每次請求時附送上這個token值
  • 服務端驗證token值,並返回資料

這個token必須要在每次請求時傳遞給服務端,它應該儲存在請求頭裡, 另外,服務端要支援CORS(跨來源資源共享)策略,一般我們在服務端這麼做就可以了Access-Control-Allow-Origin: *

那麼我們現在回到JWT的主題上。

JWT長什麼樣?

JWT是由三段資訊構成的,將這三段資訊文字用.連結一起就構成了Jwt字串。就像這樣:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT的構成

第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).

jwt的頭部承載兩部分資訊:

  • 宣告型別,這裡是jwt
  • 宣告加密的演算法 通常直接使用 HMAC SHA256

完整的頭部就像下面這樣的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

playload

載荷就是存放有效資訊的地方。這個名字像是特指飛機上承載的貨品,這些有效資訊包含三個部分

  • 標準中註冊的宣告
  • 公共的宣告
  • 私有的宣告

標準中註冊的宣告 (建議但不強制使用) :

  • iss: jwt簽發者
  • sub: jwt所面向的使用者
  • aud: 接收jwt的一方
  • exp: jwt的過期時間,這個過期時間必須要大於簽發時間
  • nbf: 定義在什麼時間之前,該jwt都是不可用的.
  • iat: jwt的簽發時間
  • jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

公共的宣告
公共的宣告可以新增任何的資訊,一般新增使用者的相關資訊或其他業務需要的必要資訊.但不建議新增敏感資訊,因為該部分在客戶端可解密.

私有的宣告
私有宣告是提供者和消費者所共同定義的宣告,一般不建議存放敏感資訊,因為base64是對稱解密的,意味著該部分資訊可以歸類為明文資訊。

定義一個payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然後將其進行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

jwt的第三部分是一個簽證資訊,這個簽證資訊由三部分組成:

  • header (base64後的)
  • payload (base64後的)
  • secret

這個部分需要base64加密後的header和base64加密後的payload使用.連線組成的字串,然後通過header中宣告的加密方式進行加鹽secret組合加密,然後就構成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

將這三部分用.連線成一個完整的字串,構成了最終的jwt:

  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是儲存在伺服器端的,jwt的簽發生成也是在伺服器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。

如何應用

一般是在請求頭裡加入Authorization,並加上Bearer標註:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

服務端會驗證token,如果驗證通過就會返回相應的資源。整個流程就是這樣的:

jwt-diagram

總結

優點

  • 因為json的通用性,所以JWT是可以進行跨語言支援的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
  • 因為有了payload部分,所以JWT可以在自身儲存一些其他業務邏輯所必要的非敏感資訊。
  • 便於傳輸,jwt的構成非常簡單,位元組佔用很小,所以它是非常便於傳輸的。
  • 它不需要在服務端儲存會話資訊, 所以它易於應用的擴充套件

安全相關

  • 不應該在jwt的payload部分存放敏感資訊,因為該部分是客戶端可解密的部分。
  • 保護好secret私鑰,該私鑰非常重要。
  • 如果可以,請使用https協議




著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。