JWT 身份認證優缺點分析以及常見問題解決方案

以前分享了一個使用 Spring Security 實現 JWT 身份認證的 Demo,文章地址:適合初學者入門 Spring Security With JWT 的 Demo。 Demo 很是簡單,沒有介紹到 JWT 存在的一些問題。因此,單獨抽了一篇文章出來介紹。爲了完成這篇文章,我查閱了不少資料和文獻,我以爲應該對你們有幫助。html

相關閱讀:前端

Token 認證的優點

相比於 Session 認證的方式來講,使用 token 進行身份認證主要有下面三個優點:web

1.無狀態

token 自身包含了身份驗證所須要的全部信息,使得咱們的服務器不須要存儲 Session 信息,這顯然增長了系統的可用性和伸縮性,大大減輕了服務端的壓力。可是,也正是因爲 token 的無狀態,也致使了它最大的缺點:當後端在token 有效期內廢棄一個 token 或者更改它的權限的話,不會當即生效,通常須要等到有效期事後才能夠。另外,當用戶 Logout 的話,token 也還有效。除非,咱們在後端增長額外的處理邏輯。redis

2.有效避免了CSRF 攻擊

CSRF(Cross Site Request Forgery)通常被翻譯爲 跨站請求僞造,屬於網絡攻擊領域範圍。相比於 SQL 腳本注入、XSS等等安全攻擊方式,CSRF 的知名度並無它們高。可是,它的確是每一個系統都要考慮的安全隱患,就連技術帝國 Google 的 Gmail 在早些年也被曝出過存在 CSRF 漏洞,這給 Gmail 的用戶形成了很大的損失。數據庫

那麼究竟什麼是 跨站請求僞造 呢?說簡單用你的身份去發送一些對你不友好的請求。舉個簡單的例子:json

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

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

致使這個問題很大的緣由就是: 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 認證常見問題以及解決辦法

1.註銷登陸等場景下 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 失效,咱們直接修改對應用戶的密鑰便可。可是,這樣相比於前兩種引入內存數據庫帶來了危害更大,好比: ① 若是服務是分佈式的,則每次發出新的 token 時都必須在多臺機器同步密鑰。爲此,你須要將必須將機密存儲在數據庫或其餘外部服務中,這樣和 Session 認證就沒太大區別了。② 若是用戶同時在兩個瀏覽器打開系統,或者在手機端也打開了系統,若是它從一個地方將帳號退出,那麼其餘地方都要從新進行登陸,這是不可取的。
  • 保持令牌的有效期限短並常常輪換 :很簡單的一種方式。可是,會致使用戶登陸狀態不會被持久記錄,並且須要用戶常常登陸。

對於修改密碼後 token 還有效問題的解決仍是比較容易的,說一種我以爲比較好的方式:使用用戶的密碼的哈希值對 token 進行簽名。所以,若是密碼更改,則任何先前的令牌將自動沒法驗證。

2.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 ,它的過時時間 token 自己的過時時間好比半個小時,另一個是 refreshToken 它的過時時間更長一點好比爲1天。客戶端登陸後,將 accessToken和refreshToken 保存在本地,每次訪問將 accessToken 傳給服務端。服務端校驗 accessToken 的有效性,若是過時的話,就將 refreshToken 傳給服務端。若是有效,服務端就生成新的 accessToken 給客戶端。不然,客戶端就從新登陸便可。該方案的不足是:① ️須要客戶端來配合;② ️用戶註銷的時候須要同時保證兩個 token 都無效;③ 從新請求獲取 token 的過程當中會有短暫 token 不可用的狀況(能夠經過在客戶端設置定時器,當accessToken 快過時的時候,提早去經過 refreshToken 獲取新的accessToken)。

總結

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

Reference

相關文章
相關標籤/搜索