JWT 超詳細分析

skyArony發表於2018-10-04

2018/10/8

  • 續簽部分補充了更多細節,簽名原理部分,經@龍捲風指出存在錯誤,已改正。

本篇文章不討論 Laravel 中 JWT 這個怎麼使用,要這方面內容的可以看我另一篇文章 JWT 完整使用詳解

在此我要從一個更深的層次來探討 JWT 在實際運用中的使用以及其優缺點,以及 JWT 和 Oauth 2.0 這兩者到底有什麼差別和聯絡。

首先我們從 Token 入手,再聯絡到 JWT,然後分析 JWT 的優缺點和使用場景,最後再聯絡到 Oauth2.0。

token 是一串字串,通常因為作為鑑權憑據,最常用的使用場景是 API 鑑權。

1. API 鑑權

那麼 API 鑑權一般有幾種方式呢?我大概整理瞭如下:

cookie + session

和平常 web 登陸一樣的鑑權方式,很常見,不再贅述。

HTTP Basic

將賬號和密碼拼接然後 base64 編碼加到 header 頭中。很顯然,因為賬號和密碼幾乎是『明文』傳輸的,而且每次請求都傳,安全性可想而知。

HTTP Digest

將賬號和密碼加上其他一些資訊拼接然後取摘要加到 header 頭中。這個安全性比上面要好一點,因為如果是取摘要的話,即使資訊段被擷取,也無法輕易破解出來(當然也是有破解的可能)。

不過其實最大的問題還是:每次請求都要對賬號、密碼取一次摘要,也就是說每次請求都要有賬號和密碼,也就是說賬號和密碼要麼快取一下,要麼就每次請求要去使用者輸一次密碼,這樣顯然不合適。同樣,上面的 Basic 也存在這樣的問題。

Token

token 透過一次登入驗證,得到一個鑑權字串,然後以後帶著這個鑑權字串進行後續操作,這樣就可以解決每次請求都要帶賬號密碼的問題,而且也不需要反覆使用賬號和密碼。

所以我們接下來主要探討 token 相對於 Cookie + Session 的認證方式有什麼優勢呢?

2. Token 的優勢

token 相對於 Cookie + Session 的優點,主要有下面兩個:

CSRF 攻擊

這個原理不多做介紹,構成這個攻擊的原因,就在於 Cookie + Session 的鑑權方式中,鑑權資料(cookie 中的 session_id)是由瀏覽器自動攜帶傳送到服務端的,藉助這個特性,攻擊者就可以透過讓使用者誤點攻擊連結,達到攻擊效果。而 token 是透過客戶端本身邏輯作為動態引數加到請求中的,token 也不會輕易洩露出去,因此 token 在 CSRF 防禦方面存在天然優勢。

適合移動應用

移動端上不支援 cookie,而 token 只要客戶端能夠進行儲存就能夠使用,因此 token 在移動端上也具有優勢。

3. Token 的種類

一般來說 token 主要三種:

  • 自定義的 token:開發者根據業務邏輯自定義的 token
  • JWT:JSON Web Token,定義在 RFC 7519 中的一種 token 規範
  • Oauth2.0:定義在 RFC 6750 中的一種授權規範,但這其實並不是一種 token,只是其中也有用到 token

以上,我仔細介紹了 API 常用的鑑權方式,以及 token 相對於 cookie + session 的優點。然後接下來仔細分析 JWT。

JWT 全稱 JSON Web Tokens ,是一種規範化的 token。可以理解為對 token 這一技術提出一套規範,是在 RFC 7519 中提出的。

1. 組成

一個 JWT token 是一個字串,它由三部分組成,頭部、載荷與簽名,中間用 . 分隔,例如:xxxxx.yyyyy.zzzzz

頭部(header)

頭部通常由兩部分組成:令牌的型別(即JWT)和正在使用的簽名演算法(如HMAC SHA256 或 RSA.)。
例如:

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

然後用 Base64Url 編碼得到頭部,即 xxxxx

載荷(Payload)

載荷中放置了 token 的一些基本資訊,以幫助接受它的伺服器來理解這個 token。同時還可以包含一些自定義的資訊,使用者資訊交換。

載荷的屬性也分三類:

  • 預定義(Registered)
  • 公有(public)
  • 私有(private)

預定義的載荷

{
  "sub": "1",
  "iss": "http://localhost:8000/auth/login",
  "iat": 1451888119,
  "exp": 1454516119,
  "nbf": 1451888119,
  "jti": "37c107e4609ddbcc9c096ea5ee76c667",
  "aud": "dev"
}

這裡面的前 7 個欄位都是由官方所定義的,也就是預定義(Registered claims)的,並不都是必需的。

  • iss (issuer):簽發人
  • sub (subject):主題
  • aud (audience):受眾
  • exp (expiration time):過期時間
  • nbf (Not Before):生效時間,在此之前是無效的
  • iat (Issued At):簽發時間
  • jti (JWT ID):編號

公有的載荷

在使用 JWT 時可以額外定義的載荷。為了避免衝突,應該使用 IANA JSON Web Token Registry 中定義好的,或者給額外載荷加上類似名稱空間的唯一標識。

私有載荷

在資訊互動的雙方之間約定好的,既不是預定義載荷也不是公有載荷的一類載荷。這一類載荷可能會發生衝突,所以應該謹慎使用。

將上面的 json 進行 Base64Url 編碼得到載荷,,即 yyyyy

關於載荷的理解:

這裡三種載荷的定義應該明確的一點是 —— 對於後兩種載荷,它並非定義了載荷的種類,然後讓你去選用哪種載荷,而是對你可能會定義出來的載荷做一個分類。

比如你定義了一個 admin 載荷,這個載荷按其分類應該是私有載荷,可能會和其他人定義的發生衝突。但如果你加了一個字首(名稱空間),如 namespace-admin,那麼這應該就算一個公有載荷了。(但其實標準並沒有定義怎麼去宣告名稱空間,所以嚴格來說,還是可能會衝突)

但是在現實中,團隊都是約定好的了要使用的載荷,這樣的話,好像根本不存在衝突的可能。那為什麼文件要這麼定義呢?我的理解是,RFC 是提出一種技術規範,出發點是一套通用的規範,考慮的範圍是所有開發者,而不僅僅侷限於一個開發者團隊。就像用 token 做認證已經是很常見的技術了,但是 JWT 的提出就相當於提出了一套較為通用的技術規範。既然是為了通用,那麼考慮在大環境下的衝突可能性也是必須的。

簽名(Signature)

簽名時需要用到前面編碼過的兩個字串,如果以 HMACSHA256 加密,就如下:

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

加密後再進行 base64url 編碼最後得到的字串就是 token 的第三部分 zzzzz

組合便可以得到 token:xxxxx.yyyyy.zzzzz

簽名的作用:保證 JWT 沒有被篡改過,原理如下:

HMAC 演算法是不可逆演算法,類似 MD5 和 hash ,但多一個金鑰,金鑰(即上面的secret)由服務端持有,客戶端把 token 發給服務端後,服務端可以把其中的頭部和載荷再加上事先共享的 secret 再進行一次 HMAC 加密,得到的結果和 token 的第三段進行對比,如果一樣則表明資料沒有被篡改。

Hash-based Message Authentication Code

2. 使用

JWT 的使用有兩種方式:

  • 加到 url 中:?token=你的token
  • 加到 header 中,建議用這種,因為在 https 情況下更安全:Authorization:Bearer 你的token

JWT 在客戶端的儲存有三種方式:

  • LocalStorage
  • SessionStorage
  • Cookie [不能設定 HTTPonly]

但是最推薦的還是第三種,因為第一二種存在跨域讀取限制,而 Cookie 使用不同的跨域策略

因為沒開 HTTPonly,所以要注意防範 XSS 漏洞。

Cookie 的跨域策略

子可以讀父,但是父不可以讀子,兄弟之間不能互相訪問。

a.xxx.com 和 b.xxx.com 可以讀 xxx.com,但是 a.xxx.com 和 b.xxx.com 不能互相讀取,xxx.com 也不能讀 a.xxx.com 和 b.xxx.com 的。

你可能會想:存 Cookie 那我不是又變得和 cookie + session 一樣了嗎?

其實不然,因為存 cookie 在這只是用到了其儲存機制,而沒有利用其去鑑權。也就是說我只是簡單存一下,並沒有期望瀏覽器帶上去 token 去鑑權,將 token 加入請求這部分操作還是我手動進行的。

3. 相對於一般 token 的優點

既然 JWT 也是一種 token,那麼它相對於普通的 token 有何優點呢?

無狀態

因為 JWT 的有效期完全與其載荷中編碼的過期時間,服務端不維護任何狀態,因此 JWT 『一般』是『無狀態』的(為什麼是一般,後面會仔細說)。無狀態最大的優勢在於三點:

  • 節省伺服器的資源:因為服務端無需維護一個狀態,因此能夠節省服務端原先儲存這些狀態所花費的資源
  • 適合分散式:因為服務端無需維護狀態,因此如果服務端是多臺伺服器組成的分散式叢集,那麼無需像『有狀態』一樣互相同步各自的狀態。
  • 時間換空間:因為 token 的校驗時透過簽名校驗來進行的,簽名校驗消耗的是 CPU 時間,而『有狀態』是需要透過客戶端提供的憑據對服務端現有的狀態進行一次查詢,消耗的是 I/O 和記憶體、磁碟空間。通常對於一個 Web 服務來說,其屬於 I/O 密集型,因此透過時間換空間這一操作,可以提高整體的硬體使用率。

編碼資料

因為 JWT 能夠在載荷中編碼了部分資訊,所以如果把常用資料編碼進去的話,能夠大大減少資料庫的查詢次數,不過有兩點需要額外注意的:

  • 載荷資訊是明文編碼的,所以不能編碼敏感資訊在裡面,如果要編碼可以先加密再編碼進去
  • token 在每次請求時都會進行傳輸,所以載荷中不能編碼過多的資訊,否則會降低傳輸效率

所以 JWT 就有四個優點了:

  • 防 CSRF
  • 適合移動應用
  • 無狀態
  • 編碼資料

前兩個是 token 的優勢,後兩個是 JWT 獨特的優勢。

既然主要使用場景是鑑權,那麼安全問題就是不得不考慮的問題了。下面對 JWT 可能需要的安全問題都進行一次深入的探討並尋求最佳的解決方案。

1. 重放攻擊

重放攻擊是透過把原先的包進行一次重放來進行攻擊的手段。需要先明確是的 cookie + session 也是存在重放攻擊的問題的。

常用的防範重放攻擊的措施主要有以下幾種:

timestamp

在請求中夾帶一個時間戳,設定較短的有效期,如果一個新來的請求的請求時間超過了請求中的有效期,則認為無效。但是這種策略也存在問題,即如果一個駭客『眼疾手快』在有效期以內將你的包進行了重放, 那就來攻擊成功。

這種策略對應到 JWT 中就是給 token 設定一個較短的有效期。

nonce

在請求中夾帶一個隨機字串,這個字串傳送到客戶端後即存入客戶端的黑名單中,如果一個新來的請求其中存在的隨機字串已經在黑名單中則認為無效。但是顯然,這個策略存在巨大的問題:服務端需要維護一個黑名單庫,這個庫的大小會隨著業務執行的時間而變得無比巨大,從而嚴重影響效率。

這種策略對應到 JWT 中就是給 token 設定一個黑名單,但是不設定有效期。

timestamp + nonce

在請求中夾帶一個隨機字串和一個時間戳,如果一個新來的請求,其隨機字串已經在黑名單中則認為無效,或者一個請求的的請求時間超過了其有效期,則也認為其無效。這樣黑名單的範圍只需設定為時間戳策略的有效期範圍即可。

這種策略對應到 JWT 中就是給 token 既設定一個黑名單,又設定一個有效期。

挑戰-應答

這個其實和 timestamp + nonce 策略一樣,只是隨機字串是有服務端生成給客戶端的,客戶端攜帶服務端所給的隨機串來請求。這樣有什麼好處呢?服務端可以透過一個加密演算法來生成這個串,使其和時間戳相關,同時客戶端又無法偽造。這樣就不需要維護黑名單了。同樣也是時間換空間的策略。但是顯然每次或幾次請求就要進行一次與預請求以得到隨機串,並不是特別方便,造成的額外消耗也有待考量。

序列號

透過在請求中嵌入一個序列號,每次請求依次加一,如果一個請求的序列號早已用過,則認為無效。但是這個要用邏輯額外一個全域性序列號,並不是特別方便。

HTTPS

終極解決方案了,HTTPS 在握手過程中會自動維護一個隱式序列號,解決了上面要自己維護序列號的問題。

注意:以上均沒有討論客戶端主動重放的問題,有興趣的同學可以自己研究一下。

2. token 被盜

因為 token 中包含了登陸狀態,因此一旦 token 被盜,那麼就會被人盜用身份。那麼 token 針對被盜的防範措施整理如下:

  • 使用 HTTPS 傳輸:從傳輸層的角度解決問題
  • HTTPOnly:從儲存層的角度解決問題,防止 XSS 攻擊竊取 cookie,但是這種方案其實存在問題,因為這樣 js 就無法讀取 token 並把它加到 header 頭中了。所以不開啟 HTTPOnly 的話必須要額外注意防範 XSS 攻擊。
  • 在 token 中嵌入客戶端指紋:透過客戶端指紋,即使駭客盜取了你的 cookie,他也無法用你的 cookie 進行請求。
  • 設定較短的 token 有效期:這樣如果 token 被盜,只要超過一定時限就無法使用。

除了安全問題,JWT 還有許多其他需要考慮的問題。

1. 登出問題

因為 JWT 是無狀態的,所以它的有效期完全由其本身決定,也就是說服務端無法讓一個 token 失效。顯然這是一個比較大的問題,對此也有諸多解決方案:

1.1 客戶端主動登出

客戶端直接刪除儲存 token 的 cookie

這種方案最為簡單,操作的結果是無論客戶端還是服務端都沒有這個 token,可問題是,這個 token 並沒有真正不可使用,而是處於一個遊離態。

黑名單策略

客戶端攜帶要登出的 token 訪問一個登出介面,服務端把 token 加入一個黑名單。

此策略是否會出現黑名單過大的問題?

答案是不會,因為黑名單隻需維護本身沒有過期但又要使其無效的 token,過期的 token 就可以不用存在黑名單了。

1.2 服務端主動登出 \ 使用者修改密碼

把 token 和 uuid 用 key-value 對儲存在 redis

這種方案看上去沒問題,但是實際上,相當於自己實現了一次 cookie + session,JWT 就失去了『無狀態』這一特性,從也會失去『無狀態』特性帶來的一系列的優點。

讓每個使用者都有一個 secret

前面講到簽發 token 的時候用到了 secret ,這種策略的思想就是讓每個使用者都有一個 secret,登出一個使用者的時候修改其 secret,即可使其前面簽發的 token 無法透過校驗而失效。

這種策略上聽上去不需要維護一個狀態,但是實際上存在更大的問題。試想一下,第一種方案是透過 uuid 在已登入使用者的 token 表中找到要登出的 token 登出。cookie + session 是透過 session_id 在已登入的使用者的 session 表中找到其對應的 session 並刪除來登出。而此方案是透過 uuid 在所有使用者(而非已登入使用者)中找到對於的 secret 修改來登出。這樣看來會發現效率更低,因為查詢範圍更大了。

預黑名單

把要登出的使用者的 uuid 和當前時間(TIME) 組成 key-value 對加入預黑名單,下次請求來時,若其 uuid 和黑名單中的對應,並且簽發時間在 TIME 之前,則將其登出。這樣查詢範圍就是未過期但又要登出的使用者。並且在實現邏輯上這個預黑名單可以和簽名的黑名單做到一起。

關於黑名單策略的補充:

有人可能會覺得黑名單也是一種狀態,用這種策略實現的 JWT 並不能算純正的無狀態。這種說法沒錯,但是考慮每次要檢索的資料範圍可以得到下面一個關係:

未過期但要提前登出的使用者或 token數 < 所有已登入使用者數 < 所有使用者數

此處的『 < 』基本可以看成『遠遠小於』,所以黑名單策略雖然也算有狀態,但是其維護的狀態數也是特別小的。

可見 『黑名單』策略能夠有效解決 JWT 的登出問題。

2. 續簽問題

session 可以自動續簽,那 token 如何實現自動續簽呢?我們先仔細分析一下在 web 和 app 環境中,token 分別如何續簽。先具體分析 web 續簽和 app 續簽分別是什麼樣的具體需求。

web

超過一段時間沒有請求,需要重新登入,這個時間一般設定為 1-2 小時

app

超過一段較長的時間沒有請求,需要重新登入,這個時間一般為 15-30 天

那這個需求可以如何實現呢?

2.1 方式一

  • 服務端接管重新整理
  • token 設定一個『過期時間』
  • token 過期後但是仍在『重新整理時間』內時仍然可重新整理
  • token 過期後超過『重新整理時間』就不能再重新整理,需重新登入

web

假設一個 token 的簽發時間為 12:00,需求為 2h 未進行請求就要重新登入。則過期時間為 1h,重新整理時間為 3h。

那麼在 12:00 - 13:00 其都是可以正常使用的,如果在 13:00 - 15:00 進行請求,服務端自動換一個新 token 給客戶端,達成續簽。

如果 13:00 -15:00 之間沒有進行請求,而是在 15:00 之後進行的請求,那麼判斷過期,需重新登入。

這樣的話,最終的實現效果是:token 過期 2h 後需要重新登入 ,而不是 token 2h 未使用需要重新登入,導致的結果是,使用者是 2 - 3h 未進行請求,需要重新登入。比設定的需求要多一個小時的不確定時間,但這也是沒辦法的辦法了,至於會不會對業務造成影響,看具體需求吧,大多數的情況還是不會的。

app

和 web 端類似,設定成更長的時間週期即可。

對使用 Laravel 開發並使用 tymon/jwt-auth 這個外掛的開發者,有個必須要注意的地方。

此處進行 token 的重新整理並不是透過 refresh 這個操作獲得新 token,因為這樣 token 在不斷的重新整理過程中會達到一個重新整理時間的上限。而上面的邏輯是每次都新簽發一個 token,只要不斷籤就能夠一直使用下去。 然後這裡的舊 token 放入黑名單,黑名單有效期設定為『重新整理時間』—— 3h。

當然如果開發者覺得這樣不斷籤就能夠一直使用不太好,那就可以設定更長的重新整理時間,用 refresh 操作來獲取新 token,重新整理時間保證每次登陸得到 token 後,即使每次及時續簽,最終也不會超過重新整理時間。

然後這裡又會出現一個新坑:

如果重新整理時間設定為 14 天,過期時間設定為 2h。

token A 在 『 <= 14天 』時重新整理得到 token B,此時若再拿 token A 去請求重新整理,肯定是不允許,否則 token 會出現『 1變 N 』的問題,所以顯然必須設定一個黑名單去放這些已過期但是又已經重新整理過的 token。而這個黑名單的有效期範圍應當為 token 的重新整理期,即 14 天。然後你會發現對於每個使用者每次登陸,需要維護的黑名單 token 數目最大可達 14 * 24 / 2 = 168 個,黑名單變得很大。

所以,如果要使用 refresh 操作,重新整理時間務必是過期時間的儘量小的倍數。

2.2 方式二

  • 每次請求 token 都進行一次重新整理
  • token 設定一個過期時間
  • token 過期後無法再重新整理
  • token 沒必要設定重新整理時間了

web

假設一個 token 的簽發時間為 12:00,需求為 2h 未進行請求即過期。則設定有效期 2h,不需要設定重新整理期。那麼每次請求都會把一個 token 換成一個新 token。如果 2h 沒有進行請求,那麼上一次請求的到的 token 就會過期,需要重新登入。同樣是不斷籤就能一直使用下去。

如果想要和上面一樣,不希望永久續簽,則設定一個重新整理時間即可。這個重新整理時間不會導致進一步膨脹。

app

和 web 端類似,設定更長時間即可。

然後又到了問題時間:

  1. 每次都重新整理 token,帶來的效能影響如何?

    以前每次請求,需要進行一次 token 簽名校驗,而現在是要簽發一個新 token,進行的都是一次簽名運算,那麼運算量即從 n 變成 2n。

    其次,每次重新整理都要把舊 token 加入黑名單,會導致黑名單特別大,遠遠比方式一的設定重新整理期大。

  2. 每次都重新整理 token,併發請求時會不會因為 token 重新整理而導致只有一個請求成功?

    答案是確實會導致這個問題,怎麼解決呢?設定一個寬限時間,每次 token 重新整理後,原來邏輯應該是立刻不可用,現在設定一個寬限時間,讓其在 n 秒之內仍然可用即可。

總之,這種策略會導致花費的 CPU 運算翻倍,並導致巨大的黑名單,然後必須設定一個寬限時間以解決併發請求問題,至於寬限時間會不會帶來安全問題,微乎其微吧。

2.3 黑名單膨脹的解決方案

上面講到,對於方式一【限定不能一直續簽】,會導致巨大的黑名單,對於方式二,總會導致一個更加巨大的黑名單。那有沒有解決方案呢?當然是有的。

我們可以這麼想,既然一個 token 進行了重新整理,那麼簽發時間在這次重新整理之前的即可認為無效。於是,和上面的『預黑名單』策略類似,我重新整理時不是把一個 token 加入黑名單,而是把 uuid-refresh_time 組成 key-vakue 對加入黑名單,這樣針對每個使用者的每次登陸,要儲存到黑名單中的條目數就從 N 個變成了一個。

但是這樣還要考慮一個問題:就是一個使用者開兩個瀏覽器,在不同的時刻在同一個系統都登陸了(假設業務允許),那麼一個瀏覽器的 token 重新整理就可能會導致另一個瀏覽器登陸失效。所以儲存在黑名單中的 key-value 應該再加一個 key 以代表每次登陸,並且這個 key 要在 JWT 的載荷中隨著重新整理一直傳承。

基於以上的最佳化,黑名單的大小變成了:每個使用者同時登陸的系統個數之和,就變的和 cookie + session 一樣了。

比如,A 系統(假設 2h 過期時間,14天重新整理時間),你用一個瀏覽器登陸了你的賬號,我用 Chrome 瀏覽器登陸了我的賬號,然後我又用 QQ 瀏覽器再登陸我的賬號,那麼黑名單的大小就為 : 1 + 2 = 3

而對於方式一【限定不能一直續簽】,黑名單的大小(最大):168 + 168 * 2

而對於方式二,黑名單的大小為:你在 2h 內請求的次數 x ,我在 Chrome 瀏覽器請求的次數 y,我在 QQ 瀏覽器請求的次數 z 之和,即:x + y + z

2.4 總結

如果要解決續簽問題,方式一【可以一直續簽】是個比較好的解決方案,雖然會帶來一點小問題,但是並不會有太大的影響。方式二【限定不能一直續簽】和 每次重新整理會讓黑名單的維護量和有狀態差不多,但是有更高的安全性。

3. token 有沒有必要每次重新整理

我們先列舉每次重新整理 token 的優缺點:

優點:

  • 能夠實現續簽
  • 能夠解決重放
  • 更安全

缺點:

  • 雙倍的 CPU 消耗
  • 幾乎和有狀態一樣的空間消耗
  • 必須設定寬限時間解決併發問題

上面討論過,『續簽』和『重放』都可以透過其他方式解決。只有『更安全』算半個痛點,為什麼是半個痛點呢?因為如果採用 HTTPS 的話,那麼盜取 token 的手段就只要以下幾種辦法:

  • 破解 HTTPS
  • 直接從你電腦上手抄過去
  • XSS【前面說到為了能夠讓 js 讀取,不能設定 HTTPOnly】

只有第三種方法存在一點可能性。

所以,要不要每次重新整理,還是根據各位的具體業務情況進行選擇吧。

1. 無狀態的 RESTful API

這個顯然很適合。

2. SSO 單點登入

單點登入必須要實現的:

  • 會話管理:透過黑名單和預黑名單解決
  • 續簽:透過簽名的解決方案解決

可見,對 JWT 部署一些額外邏輯(黑名單,續簽管理)即可讓 JWT 在大部分場景代替 cookie + session。

Oauth 2.0 是幹嘛的不再贅述,它與 JWT 其實並不是一個層面的東西。Oauth2.0 是一個方便的第三方授權規範,而 JWT 是一個 token 結構規範。只是 JWT 常用來登陸鑑權,而 Oauth2.0 在授權時也涉及到了登陸,所以就比較容易搞混。

但是在此,我要說的是,Oauth 2.0 其實可以和 JWT 結合使用。

以下是一個常見的 Oauth2.0 登陸返回:

{
    "access_token":"kag2geh11a3eh56e23hj",
    "expires_in":7200,
    "refresh_token":"jgko97cq4c8wn69j",
    "scope":"SCOPE" 
}

在 Oauth2.0 中,access_token 用來進行資料請求,而 refresh_token 用來重新整理 access_token。每次重新整理,上一個 access_token 就會失效,而 access_tokenrefresh_token 顯然都沒有記錄任何狀態,所以必須為服務端進行狀態的維護。

把 JWT 和 Oauth2.0 結合後,可以得到這樣的返回:

{
    "access_token":"xxx.yyy.zzz",
    "expires_in":7200,
    "refresh_token":"xxxxx.yyyyy.zzzzz",
    "scope":"SCOPE" 
}

進行結合後有如下優勢:

  • Oauth2.0 的 token 也能夠實現無狀態(雖然也要用到黑名單)
  • Oauth2.0 的 token 也能夠附帶部分常用資料
  • 前面講到 JWT 續簽,在需要限定不能一直續簽的情形,可能會導致黑名單庫膨脹,但是和 Oauth2.0 結合,透過 refresh_token 的機制,讓黑名單庫中 token 的有效期從 『重新整理時間』又變回『過期時間』,從而解決了這個問題。

這是我從 Auth0 組織的這篇文章 10 Things You Should Know about Tokens 整理過來的:

  1. Token 獲取到後需要儲存起來以便下次使用,可以選擇儲存在 localstorage / sessionstorage / cookie

  2. Token 是包含有效期的,你必須部署一些邏輯來進行有效期的控制

  3. localstorage / sessionstorage 的跨域限制較 cookie 更為嚴格,推薦使用 cookie

  4. 在你進行非同步請求時,瀏覽器一般都會傳送預檢請求(option),後端應對此部署相應的邏輯

    為什麼會有OPTIONS請求 - 雲+社群 - 騰訊雲

  5. 使用 cookie 可以輕鬆處理一個檔案下載請求,但是 token 一般都是透過 XHR 方式進行請求的,所以你必須部署額外的邏輯。比如生成一個實時 ticket ,以 ticket 進行訪問,然後校驗,重定向,最後下載檔案。

  6. 處理 XSS 比處理 CSRF 更容易(這一點我實在沒看到他是什麼個邏輯,大家可以去看看原文)

  7. token 在每次請求時都會被編碼到請求中,所以請注意 token 的大小,不要編碼過多資料

  8. 如果在 token 中編碼敏感資訊,請對 token 進行加密

  9. JSON Web Token 可以用於 Oauth2.0 的 Bearer Token 中,賦予 Oauth2.0 無狀態的優勢

  10. Token 不是銀彈,請根據實際業務需要進行選擇

前陣子寫的兩篇文章,承蒙各位關照,指出了許多問題。最近公司分享,我恰好打算對以前提出的問題結合我新的理解寫一篇文章,於是便有了本文,前面兩篇文章,近期我會重新整理一下,補上一些細節,修復一些錯誤,各位若不嫌棄到時候可以再看看。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章