JWT (JSON Web Token) - 原理介紹

自從 RESTful 架構興起後,越來越多人提倡使用 JWT 來取代傳統 Session 的場景,到底什麼是 JWT 呢,來看看吧~

JWT 介紹

  1. JWT 是 JSON Web Token 的簡寫,是一種開放標準 (RFC 7519),也就是基於 JSON object 的編碼,並透過這個編碼進行傳遞資訊。
  2. JWT 會透過 HMAC、RSA、ECDS 等演算法進行加密
  3. 通常利用 JWT 來對使用者進行驗證,也就是使用者會先請求身分提供的伺服器給予該 JWT,而後,只要使用者帶著這個 JWT 向資源伺服器請求資源,如果這個 JWT 是有效的,那麼就能獲取資源

JWT 的組成

JWT 的組合可以看成是三個 JSON object,並且用 **.** 來做區隔,而這三個部分會各自進行編碼,組成一個 JWT 字串。

也就是變成:xxxxx.yyyyy.zzzzz

  • Header

    由兩個欄位組合:

    1. alg

      也就是 token 被加密的演算法,如 HMACSHA256RSA

    2. typ

      也就是 token 的 type,基本上就是 JWT

    範例:

    1
    2
    3
    4
    {
    "alg": "HS256",
    "typ": "JWT"
    }

    然後進行 Base64 進行編碼。Base64 是透過 64 個字符來表示二進制數據的一種方法,編碼的方式是固定的而且是可以逆向解碼的,並不是那種安全的加密演算法。

  • Payload

    這裡放的是聲明 (Claim) 內容,也就是用來放傳遞訊息的地方,在定義上有三種聲明:

    1. Registered claims

      可以想成是標準公認的一些訊息建議你可以放,但並不強迫,例如:

      • iss (Issuer):JWT 簽發者
      • exp (Expiration Time):JWT 的過期時間,過期時間必須大於簽發 JWT 時間
      • sub (Subject):JWT 所面向的用戶
      • aud (Audience):接收 JWT 的一方
      • nbf (Not Before):也就是定義擬發放 JWT 之後,的某段時間點前該 JWT 仍舊是不可用的
      • iat (Issued At):JWT 簽發時間
      • jti (JWT Id):JWT 的身分標示,每個 JWT 的 Id 都應該是不重複的,避免重複發放
    2. Public claims

      這個,可以想成是傳遞的欄位必須是跟上面 Registered claims 欄位不能衝突,然後可以向官方申請定義公開聲明,會進行審核等步驟,實務上在開發上是不太會用這部分的。

    3. Private claims

      這個就是發放 JWT 伺服器可以自定義的欄位的部分,例如實務上會放 User Account、User Name、User Role 等不敏感的數據。

      所謂不敏感的數據就是不會放使用者的密碼等敏感數據,因為該 Payload 傳遞的訊息最後也是透過 Base64 進行編碼,所以是可以被破解的,因此放使用者密碼會有安全性的問題。

    範例:

    1
    2
    3
    4
    5
    {
    "sub": "1234567890",
    "account": "[email protected]",
    "role": "admin"
    }

    個人覺得通常都會放 iat、exp 等標準欄位,因為通常會需要檢查 JWT 發送時間及是否過期,以及還有使用者帳號,為了方便查詢使用者的一些數據,通常以前的做法是 Session 裡面存放使用者帳號,現在改用 JWT 的 payload 上存放,以及角色身分的定義,可以用來看該使用者是否有權限取得後端 API 的內容。

  • Signature

    由三大部分組成:

    • base64UrlEncode(header)
    • base64UrlEncode(payload)
    • secret

    也就是:

    1
    2
    3
    4
    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)

    header 跟 payload 中間用 **.** 來串接,secret 是存放在伺服器端的秘密字串,最後將這三個部分串接再一起的字串進行加密演算法進行加密。

    Note:

    secret 是要保存在伺服器端的,這個 secret 一旦外洩給客戶端,客戶端就可以自己產生 JWT,並且透過該 JWT 存取資源,因此 secret 是永遠不該外流的。

最後將 Header、Payload、Signature 三者用 **.** 串聯在一起,就是一個合法簽發的 JWT 字串。

在 JWT 官網:https://jwt.io/

有提供現成的 JWT 簽發工具,可以使用該工具來看產生的 JWT 字串長怎樣:

我使用上面我寫的三個部份的內容各自填上去,左邊就會出現正確的 JWT 字串出來。紅色部分就是編碼後的 Header、紫色部分就是編碼後的 Payload,藍色部分就是將 Header、Payload、Secrect 合在一起並進行加密演算法加密後的編碼。最後就是用 **.** 來將三個部分串聯在一起。

客戶端如何用 JWT 來訪問資源?

  1. 前端會先透過存取後端的登入 API,後端驗證使用者帳密成功後,就會發放合法 JWT 字串

  2. 前端拿到 JWT 字串就會將 JWT 存放在 Local Storage 裡面

  3. 而後當前端要存取受保護的資源 API 時,只要在 Header 的填寫以下內容:

    1
    Authorization: Bearer <JWT token>
  4. 後端收到後,會去檢查 Authorization 的 JWT token 是否有效,如果有效,則允許前端訪問受保護的資源。在以前的 Session 的設計上,Session 會存放在 Redis 等這種快取資料庫,每當使用者訪問受保護的資源時,會先去存取資料庫的 Session 進行比對,有效則讓使用者存取,以 JWT 的方式可以降低查詢資料庫的需求。

JWT 的優缺點

優點:

  • 採用 JSON object 的形式,大部分的程式語言皆支援
  • 可存放一些使用者資訊,但並非是敏感的資訊
  • 整個 JWT,只要 Payload 不要放過多的資訊,其實 Size 是相當小的
  • 不用在 Server 的資料庫存放 Session,特別適合多台 Server 的情境下,使得擴展性容易,因為多台 Server 要使用 Session 的話,會有共享 Session 的問題產生,
  • 對於現在手機上的 APP 的應用特別好,使用者不用每次打開 APP 都要重新輸入帳號與密碼
  • 支持跨域請求,不會有傳統用 Cookie 進行跨域請求等問題

缺點:

  • JWT 沒辦法主動被中止,也就是說不能像 Session 一樣被強制無效,但是個人覺得這有很多方式可以避免

  • JWT 一旦洩漏會有很大的安全性的問題,但是洩漏通常會透過兩種方式:

    1. 駭客使用你的電腦,並得知 JWT

      這… 你電腦都被攻陷了… 那就不好說了 XD

    2. 使用中間人攻擊的方式,擷取客戶端傳送伺服器端的封包,並獲取 JWT,但使用 HTTPS 傳輸可以大幅度降低該攻擊,只要定期更換 SSL 證書就可以了

總結

JWT 之所以會興起,除了因為 RESTful 架構出現,加上現在微服務的架構的關係,一般來說上線的系統,不太可能用單台伺服器來處理一切,多台伺服器處理 Session 會有其麻煩性,雖然可以用統一的資料庫進行存放 Session 來控制,但是會有效能的問題。

不過其實這還有很多探討的空間,在這篇文章沒有去說,Session/Cookie 的架構其實也是可以跟 JWT 並行,端看應用場景。但就我兩者都使用過的經驗來看,我覺得 JWT 的方式的確很簡單易懂,Session 的機制有時候的確很煩人。

下次帶來有關 JWT 的一些語言相關 Library 的使用方式~以及在 RESTful 場景下使用方式。

最後最後!請聽我一言!

如果你還沒有註冊 Like Coin,你可以在文章最下方看到 Like 的按鈕,點下去後即可申請帳號,透過申請帳號後可以幫我的文章按下 Like,而 Like 最多可以點五次,而你不用付出任何一塊錢,就能給我寫這篇文章的最大的回饋!