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

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

基於 Session 的會話管理

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

  • 客戶端使用用戶名密碼進行認證
  • 服務端生成並存儲 Session,將 SessionID 經過 Cookie 返回給客戶端
  • 客戶端訪問須要認證的接口時在 Cookie 中攜帶 SessionID
  • 服務端經過 SessionID 查找 Session 並進行鑑權,返回給客戶端須要的數據

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

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

基於 Token 的會話管理

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

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

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

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

JWT 介紹

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

一個 JWT Token 長這樣。bash

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 規範規定了一些字段,並推薦使用,開發者也能夠本身指定字段和內容,例以下面的內容。app

{
  username: 'yage',
  email: 'sa@simpleapples.com',
  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 訪問須要認證的接口

將生成的 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 管理的精度符合業務的需求。

關注公衆號【Python私房菜】

相關文章
相關標籤/搜索