系統設計 JWT

JWT 本質上就是JWT 本質上就一段簽名的 JSON 格式的數據。由於它是帶有簽名的,因此接收者便可以驗證它的真實性。。由於它是帶有簽名的,因此接收者可以驗證它的真實性。

JWT 由 3 部分構成:

  1. Header :描述 JWT 的元數據。定義了生成簽名的算法以及 Token 的類型。
  2. Payload(負載):用來存放實際需要傳遞的數據。
  3. Signature(簽名):服務器通過 PayloadHeader 和一個密鑰( secret )使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256)生成。

在基於 Token 進行身份驗證的的應用程序中,服務器通過 PayloadHeader 和一個密鑰( secret )創建令牌(Token)並將 Token 發送給客戶端,客戶端將 Token 保存在 Cookie 或者 local storage 裏面,之後客戶端發出的所有請求都會攜帶這個令牌。可以把它放在 Cookie 裏面自動發送,但是這樣不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization 字段中:Authorization: Bearer Token

Token Based Authentication flow

基於 JWT 的認證過程

  1. 用戶向服務器發送用戶名和密碼用於登陸系統。
  2. 身份驗證服務響應並返回了簽名的 JWT,上面包含了用戶是誰的內容。
  3. 用戶以後每次向後端發請求都在 Header 中帶上 JWT。
  4. 服務端檢查 JWT 並從中獲取用戶相關信息。

認證 (Authentication) 與授權 (Authorization)

說簡單點就是:

  • 認證 (Authentication): 你是誰。
  • 授權 (Authorization): 你有權限幹什麼。

稍微正式點的說法就是:

  • Authentication(認證) 是驗證身份的憑據(例如用戶名 / 用戶 ID 和密碼),通過這個憑據,系統得以知道你就是你,也就是說系統存在你這個用戶。所以,Authentication 被稱爲身份/用戶驗證。
  • Authorization(授權) 發生在 **Authentication(認證)**之後。授權主要掌管用戶訪問系統的權限。比如有些特定資源只能具有特定權限的人才能訪問比如 admin,有些對系統資源操作比如刪除、添加、更新只能由特定的人進行。

這兩個一般在系統中被結合在一起使用,目的就是爲了保護系統的安全性。

Token 認證的優勢

1.無狀態

token 自身包含了身份驗證所需要的所有信息,使得服務器不需要存儲 Session 信息,這顯然增加了系統的可用性伸縮性,大大減輕了服務端的壓力。但是,也正是由於 token 的無狀態,也導致了它最大的缺點:當後端在token 有效期內廢棄一個 token 或者更改它的權限的話,不會立即生效,一般需要等到有效期過後纔可以。另外,當用戶 Logout 的話,token 也還有效。除非,我們在後端增加額外的處理邏輯。

2.避免 CSRF 攻擊

**CSRF(Cross Site Request Forgery)**一般被翻譯爲 跨站請求僞造,屬於網絡攻擊領域範圍。相比於 SQL 腳本注入、XSS 等等安全攻擊方式,CSRF 的知名度並沒有它們高。但是,它的確是每個系統都要考慮的安全隱患。

跨站請求僞造簡單說就是用你的身份去發送一些對你不友好的請求。舉個簡單的例子:

小明登錄了某網上銀行,他來到了網上銀行的帖子區,看到一個帖子下面有一個鏈接寫着“科學理財,年盈利率過萬”,小壯好奇的點開了這個鏈接,結果發現自己的賬戶少了10000元。這是這麼回事呢?原來黑客在鏈接中藏了一個請求,這個請求直接利用小壯的身份給銀行發送了一個轉賬請求,也就是通過你的 Cookie 向銀行發出請求。

<a src=http://www.mybank.com/Transfer?bankId=11&money=10000>科學理財,年盈利率過萬</>

導致這個問題很大的原因就是:Session 認證中 Cookie 中的 session_id 是由瀏覽器發送到服務端的,藉助這個特性,攻擊者就可以通過讓用戶誤點攻擊鏈接,達到攻擊效果。

那爲什麼 token 不會存在這種問題呢?

一般情況下我們使用 JWT 的話,在我們登錄成功獲得 token 之後,一般會選擇存放在 local storage 中。然後我們在前端通過某些方式會給每個發到後端的請求加上這個 token,這樣就不會出現 CSRF 漏洞的問題。因爲,即使有個你點擊了非法鏈接發送了請求到服務端,這個非法請求是不會攜帶 token 的,所以這個請求將是非法的。

但是這樣會存在 XSS 攻擊中被盜的風險,爲了避免 XSS 攻擊,你可以選擇將 token 存儲在標記爲 httpOnly 的cookie 中。但是,這樣又導致了你必須自己提供 CSRF 保護。

具體採用上面哪兩種方式存儲 token 呢,大部分情況下存放在 local storage 下都是最好的選擇,某些情況下可能需要存放在標記爲 httpOnly 的cookie 中會更好。

3.適合移動端應用

使用 Session 進行身份認證的話,需要保存一份信息在服務器端,而且這種方式會依賴到 Cookie(需要 Cookie 保存 SessionId),所以不適合移動端。

但是,使用 token 進行身份認證就不會存在這種問題,因爲只要 token 可以被客戶端存儲就能夠使用,而且 token 還可以跨語言使用。

4.單點登錄友好

使用 Session 進行身份認證的話,實現單點登錄,需要我們把用戶的 Session 信息保存在一臺電腦上,並且還會遇到常見的 Cookie 跨域的問題。但是使用 token 進行認證的話, token 被保存在客戶端,不會存在這些問題。

Token 認證常見問題以及解決辦法

註銷登錄等場景下 token 還有效

與之類似的具體相關場景有:

  1. 退出登錄;
  2. 修改密碼;
  3. 服務端修改了某個用戶具有的權限或者角色;
  4. 用戶的帳戶被刪除/暫停。
  5. 用戶由管理員註銷;

這個問題不存在於 Session 認證方式中,因爲在 Session 認證方式中,遇到這種情況的話服務端刪除對應的 Session 記錄即可。但是,使用 token 認證的方式就不好解決了。token 一旦派發出去,如果後端不增加其他邏輯的話,它在失效之前都是有效的。那麼,我們如何解決這個問題呢?有如下幾種方案:

  • 將 token 存入內存數據庫:將 token 存入 DB 中,redis 內存數據庫在這裏是不錯的選擇。如果需要讓某個 token 失效就直接從 redis 中刪除這個 token 即可。但是,這樣會導致每次使用 token 發送請求都要先從 DB 中查詢 token 是否存在的步驟,而且違背了 JWT 的無狀態原則。
  • 黑名單機制:和上面的方式類似,使用內存數據庫比如 redis 維護一個黑名單,如果想讓某個 token 失效的話就直接將這個 token 加入到 黑名單 即可。然後,每次使用 token 進行請求的話都會先判斷這個 token 是否存在於黑名單中。
  • 修改密鑰 (Secret) : 我們爲每個用戶都創建一個專屬密鑰,如果我們想讓某個 token 失效,我們直接修改對應用戶的密鑰即可。但是,這樣相比於前兩種引入內存數據庫帶來了危害更大,比如:1. 如果服務是分佈式的,則每次發出新的 token 時都必須在多臺機器同步密鑰。爲此,你需要將必須將密鑰存儲在數據庫或其他外部服務中,這樣和 Session 認證就沒太大區別了。2. 如果用戶同時在兩個瀏覽器打開系統,或者在手機端也打開了系統,如果它從一個地方將賬號退出,那麼其他地方都要重新進行登錄,這是不可取的。
  • 保持令牌的有效期限短並經常輪換 :很簡單的一種方式。但是,會導致用戶登錄狀態不會被持久記錄,而且需要用戶經常登錄。

對於修改密碼後 token 還有效問題的解決還是比較容易的,其中一種方式:使用用戶的密碼的哈希值對 token 進行簽名。因此,如果密碼更改,則任何先前的令牌將自動無法驗證。

token 的續簽問題

token 有效期一般都建議設置的不太長,那麼 token 過期後如何認證,如何實現動態刷新 token,避免用戶經常需要重新登錄?

我們先來看看在 Session 認證中一般的做法:假如 session 的有效期30分鐘,如果 30 分鐘內用戶有訪問,就把 session 有效期被延長30分鐘。

  1. 類似於 Session 認證中的做法:這種方案滿足於大部分場景。假設服務端給的 token 有效期設置爲30分鐘,服務端每次進行校驗時,如果發現 token 的有效期馬上快過期了,服務端就重新生成 token 給客戶端。客戶端每次請求都檢查新舊token,如果不一致,則更新本地的 token。這種做法的問題是僅僅在快過期的時候請求才會更新 token , 對客戶端不是很友好。
  2. 每次請求都返回新 token :這種方案的的思路很簡單,但是很明顯,開銷會比較大。
  3. token 有效期設置到半夜 :這種方案是一種折衷的方案,保證了大部分用戶白天可以正常登錄,適用於對安全性要求不高的系統。
  4. 用戶登錄返回兩個 token :第一個是 acessToken ,它的過期時間比如是半個小時,另外一個是 refreshToken 它的過期時間更長一點比如爲 1 天。客戶端登錄後,將 accessToken 和 refreshToken 保存在本地,每次訪問將 accessToken 傳給服務端。服務端校驗 accessToken 的有效性,如果過期的話,就將 refreshToken 傳給服務端。如果有效,服務端就生成新的 accessToken 給客戶端。否則,客戶端就重新登錄即可。該方案的不足是:1. 需要客戶端來配合;2. 用戶註銷的時候需要同時保證兩個 token 都無效;3. 重新請求獲取 token 的過程中會有短暫 token 不可用的情況(可以通過在客戶端設置定時器,當accessToken 快過期的時候,提前去通過 refreshToken 獲取新的accessToken)。

總結

JWT 最適合的場景是不需要服務端保存用戶狀態的場景,比如如果考慮到 token 註銷和 token 續簽的場景話,沒有特別好的解決方案,大部分解決方案都給 token 加上了狀態,這就有點類似 Session 認證了。

參考鏈接:

https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/authority-certification/JWT-advantages-and-disadvantages.md

https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485626&idx=1&sn=3247aa9000693dd692de8a04ccffeec1&chksm=cea24771f9d5ce675ea0203633a95b68bfe412dc6a9d05f22d221161147b76161d1b470d54b3&token=684071313&lang=zh_CN&scene=21#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章