基於 JWT + Refresh Token 的用戶認證實踐

HTTP 是一個無狀態的協議,一次請求結束後,下次在發送服務器就不知道這個請求是誰發來的了(同一個 IP 不代表同一個用戶),在 Web 應用中,用戶的認證和鑑權是非常重要的一環,實踐中有多種可用方案,並且各有千秋。

基於 Session 的會話管理

在 Web 應用發展的初期,大部分採用基於 Session 的會話管理方式,邏輯如下。

  • 客戶端使用用戶名密碼進行認證
  • 服務端生成並存儲 Session,將 SessionID 通過 Cookie 返回給客戶端
  • 客戶端訪問需要認證的接口時在 Cookie 中攜帶 SessionID
  • 服務端通過 SessionID 查找 Session 並進行鑑權,返回給客戶端需要的數據
基於 JWT + Refresh Token 的用戶認證實踐

基於 Session 的方式存在多種問題。

  • 服務端需要存儲 Session,並且由於 Session 需要經常快速查找,通常存儲在內存或內存數據庫中,同時在線用戶較多時需要佔用大量的服務器資源。
  • 當需要擴展時,創建 Session 的服務器可能不是驗證 Session 的服務器,所以還需要將所有 Session 單獨存儲並共享。
  • 由於客戶端使用 Cookie 存儲 SessionID,在跨域場景下需要進行兼容性處理,同時這種方式也難以防範 CSRF 攻擊。

基於 Token 的會話管理

鑑於基於 Session 的會話管理方式存在上述多個缺點,無狀態的基於 Token 的會話管理方式誕生了,所謂無狀態,就是服務端不再存儲信息,甚至是不再存儲 Session,邏輯如下。

  • 客戶端使用用戶名密碼進行認證
  • 服務端驗證用戶名密碼,通過後生成 Token 返回給客戶端
  • 客戶端保存 Token,訪問需要認證的接口時在 URL 參數或 HTTP Header 中加入 Token
  • 服務端通過解碼 Token 進行鑑權,返回給客戶端需要的數據
基於 JWT + Refresh Token 的用戶認證實踐

基於 Token 的會話管理方式有效解決了基於 Session 的會話管理方式帶來的問題。

  • 服務端不需要存儲和用戶鑑權有關的信息,鑑權信息會被加密到 Token 中,服務端只需要讀取 Token 中包含的鑑權信息即可
  • 避免了共享 Session 導致的不易擴展問題
  • 不需要依賴 Cookie,有效避免 Cookie 帶來的 CSRF 攻擊問題
  • 使用 CORS 可以快速解決跨域問題

JWT 介紹

JWT 是 JSON Web Token 的縮寫,JWT 本身沒有定義任何技術實現,它只是定義了一種基於 Token 的會話管理的規則,涵蓋 Token 需要包含的標準內容和 Token 的生成過程。

一個 JWT Token 長這樣。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDQ1MTE3NDMsImp0aSI6IjYxYmVmNjkyLTE4M2ItNGYxYy1hZjE1LWUwMDM0MTczNzkxOSJ9.CZzB2-JI1oPRFxNMaoFz9-9cKGTYVXkOC2INMoEYNNA

仔細辨別會發現它由 A.B.C 三部分組成,這三部分依次是頭部(Header)、負載(Payload)、簽名(Signature),頭部和負載以 JSON 形式存在,這就是 JWT 中的 JSON,三部分的內容都分別單獨經過了 Base64 編碼,以 . 拼接成一個 JWT Token。

JWT 的 Header 中存儲了所使用的加密算法和 Token 類型。

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

Payload 是負載,JWT 規範規定了一些字段,並推薦使用,開發者也可以自己指定字段和內容,例如下面的內容。

{
username: 'yage',
email: '[email protected]',
role: 'user',
exp: 1544602234
}

需要注意的是,Payload的內容只經過了 Base64 編碼,對客戶端來說當於明文存儲,所以不要放置敏感信息。

Signature 部分用來驗證 JWT Token 是否被篡改,所以這部分會使用一個 Secret 將前兩部分加密,邏輯如下。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

JWT 優勢 & 問題

JWT 擁有基於 Token 的會話管理方式所擁有的一切優勢,不依賴 Cookie,使得其可以防止 CSRF 攻擊,也能在禁用 Cookie 的瀏覽器環境中正常運行。

而 JWT 的最大優勢是服務端不再需要存儲 Session,使得服務端認證鑑權業務可以方便擴展,避免存儲 Session 所需要引入的 Redis 等組件,降低了系統架構複雜度。但這也是 JWT 最大的劣勢,由於有效期存儲在 Token 中,JWT Token 一旦簽發,就會在有效期內一直可用,無法在服務端廢止,當用戶進行登出操作,只能依賴客戶端刪除掉本地存儲的 JWT Token,如果需要禁用用戶,單純使用 JWT 就無法做到了。

基於 JWT 的實踐

既然 JWT 依然存在諸多問題,甚至無法滿足一些業務上的需求,但是我們依然可以基於 JWT 在實踐中進行一些改進,來形成一個折中的方案,畢竟,在用戶會話管理場景下,沒有銀彈。

前面講的 Token,都是 Access Token,也就是訪問資源接口時所需要的 Token,還有另外一種 Token,Refresh Token,通常情況下,Refresh Token 的有效期會比較長,而 Access Token 的有效期比較短,當 Access Token 由於過期而失效時,使用 Refresh Token 就可以獲取到新的 Access Token,如果 Refresh Token 也失效了,用戶就只能重新登錄了。

在 JWT 的實踐中,引入 Refresh Token,將會話管理流程改進如下。

  • 客戶端使用用戶名密碼進行認證
  • 服務端生成有效時間較短的 Access Token(例如 10 分鐘),和有效時間較長的 Refresh Token(例如 7 天)
  • 客戶端訪問需要認證的接口時,攜帶 Access Token
  • 如果 Access Token 沒有過期,服務端鑑權後返回給客戶端需要的數據
  • 如果攜帶 Access Token 訪問需要認證的接口時鑑權失敗(例如返回 401 錯誤),則客戶端使用 Refresh Token 向刷新接口申請新的 Access Token
  • 如果 Refresh Token 沒有過期,服務端向客戶端下發新的 Access Token
  • 客戶端使用新的 Access Token 訪問需要認證的接口
基於 JWT + Refresh Token 的用戶認證實踐

將生成的 Refresh Token 以及過期時間存儲在服務端的數據庫中,由於 Refresh Token 不會在客戶端請求業務接口時驗證,只有在申請新的 Access Token 時才會驗證,所以將 Refresh Token 存儲在數據庫中,不會對業務接口的響應時間造成影響,也不需要像 Session 一樣一直保持在內存中以應對大量的請求。

上述的架構,提供了服務端禁用用戶 Token 的方式,當用戶需要登出或禁用用戶時,只需要將服務端的 Refresh Token 禁用或刪除,用戶就會在 Access Token 過期後,由於無法獲取到新的 Access Token 而再也無法訪問需要認證的接口。這樣的方式雖然會有一定的窗口期(取決於 Access Token 的失效時間),但是結合用戶登出時客戶端刪除 Access Token 的操作,基本上可以適應常規情況下對用戶認證鑑權的精度要求。

總結

JWT 的使用,提高了開發者開發用戶認證鑑權功能的效率,降低了系統架構複雜度,避免了大量的數據庫和緩存查詢,降低了業務接口的響應延遲。然而 JWT 的這些優點也增加了 Token 管理上的難度,通過引入 Refresh Token,既能繼續使用 JWT 所帶來的優勢,又能使得 Token 管理的精度符合業務的需求。

鏈接:https://www.jianshu.com/p/25ab2f456904


分享到:


相關文章: