引言
近來發現,很多 WEB 應用系統使用 JWT 進行會話管理,原因居然是爲了不服務端存儲會話,或者追求可自主控制,實不知使用 JWT 進行會話管理有巨大的安全隱患!html
HTTP 會話管理
先說說 HTPP 協議,衆所周知, HTTP 協議的特色就是一問一答(一次請求一次響應)。那麼基於 HTTP 協議,作個購物網站,要實現用戶登陸,添加商品到購物車,最終買單的功能。爲了實現以上功能,識別出一系列操做都是同一個用戶,最爲簡單的方式就是每次操做瀏覽器都發送帳戶和密碼,這樣服務端知道是哪一個用戶添加商品到購物車和最終買單。這樣看起來很簡陋,並且在每次請求中攜帶帳戶和密碼將大大增長泄露風險。 git
爲了減小帳戶密碼在請求中攜帶的次數,咱們引入一個 session-id 的東西。在你攜帶帳戶密碼登陸購物網站(第一次請求),服務端驗證經過後返回一個 session-id 。此後你添加商品到購物車也好,最終下單也好,反正後續的每次請求都攜帶好這個 session-id 便可。爲何攜帶 session-id 就能夠識別出這是哪一個客戶的操做呢?由於服務端將客戶的帳戶信息和本次會話發送給客戶端的 session-id 關聯了起來,那麼這個session-id 和帳戶的關聯信息就是 session-data。 github
在以上例子裏,session-data 是存儲在服務端的。其實,session-data 也能夠存儲到客戶端。那麼根據 session-data 存儲的位置不一樣,分爲 Server Side Session 和 Client Side Session 兩種模式。web
Server Side Session
Server Side Session 模式是最爲常見的,如前面購物網站的例子。在此模式中,客戶端(瀏覽器)將 session-id 存儲到 本地 cookies 中。其典型實現以下圖所示: 算法
上圖中 User A 是個已經創建會話的老用戶,他的 Cookies 中存儲了 Session_id,值爲「AA01」,在請求中攜帶這個 Session_id,服務端收到後經過 Session_id 查詢到 Session Database 中對應的數據,並在程序中使用。New User是個剛開始會話的新用戶,他的 Cookies 中尚未 Session_id,因而服務端生成一個新的 session_id 返回給他,並在返回中使用401狀態碼,告知他重定向到登陸頁,進行登陸。後續若是這個 New User 登陸成功,服務端將會把他的身份信息存儲到 Session Table中,並與會話初生成的 session_id 關聯,這樣後續的請求就如同 User A 這個老用戶。json
Client Side Session
Client Side Session 模式你們可能以爲有點陌生,其實用的不少。如大部分使用 JWT 進行會話管理的WEB系統,就是 Client Side Session 模式。所謂 Client Side Session 模式就是把用戶的 session-data 數據存儲在客戶端,這樣服務端就很是悠閒。其交互流程大體以下圖所示: 後端
上圖中左側 User A 是個已經創建會話的老用戶,他的 Cookies 中存儲了 Session_data,值爲當前用戶的基本信息,在請求中攜帶着 Session_data 數據,服務端收到請求後解析出 Session_data 信息,便知道是哪一個用戶的請求。右側的 New User 用戶正在登陸創建會話,服務端驗證成功後,將生成 session_data 信息,返回給客戶端。因爲 session_data 數據是存儲在客戶端,爲了防止篡改,服務端會對生成的 session_data 數據進行簽名,甚至加密。瀏覽器
Server Side VS Client Side
前文介紹了 Server Side Session 和 Client Side Session 兩種模式各自的原理,那麼這兩種模式孰優孰劣呢?在對比以前,先定一個基準,就是僅限功能性和安全性,而不是對比兩種模式的原始實現性。安全
Server Side Session
優勢:服務器
- 會話數據存儲在服務端,不暴露相關信息,安全性高;
- 每次請求只需傳遞 session-id,減小流量開銷;
- 服務端可方便吊銷會話,控制同帳戶會話併發數等全面的會話策略管理;
缺點:
- 服務端分佈式部署時,需增長處理 Session-data 共享。
Client Side Session
優勢:
- 分散存儲;
缺點:
- 會話數據存儲在客戶端,且在每次請求中攜帶,增長泄露風險;
- 每次請求需傳遞更多數據,增長流量開銷;
- 服務端不方便吊銷會話,實現各類會話策略管理;
從以上對比能夠看出,兩種模式在功能性和安全性上的特色恰好相反。Server Side Session 模式優勢突出,但爲何還存在Client Side Session 模式呢?所謂存在即合理,由於 Client Side Session 模式原始實現簡單!Client Side Session 模式服務端無需集中存儲 session-data 數據,天然也無需處理 session-data 的查找,並且分散存儲在客戶端,服務端無需考慮分佈式下的 session-data 共享。 可是 Client Side Session 模式原始實現簡單對於使用者來講並不重要,由於使用者只使用現成的庫和框架,只要支持度好就行。因此,Server Side Session 比 Client Side Session 模式更爲廣泛。
什麼是 JWT ?
首先說明 JWT(JWE、JWS)標準只是設計了一個防篡改令牌,並不是是爲會話管理而設計。
JSON Web Token(JWT)是一個基於 RFC 7519 的開放數據標準,它定義了一種寬鬆且緊湊的數據組合方式,使用 JSON 對象在各應用之間傳輸信息(信息加密即 jwe,簽名即 jws)。該 JSON 對象能夠經過數字簽名進行鑑籤和校驗,通常地,JWT 能夠採用 HMAC 算法,RSA 或者 ECDSA 的公鑰/私鑰對數據進行簽名操做。一個 JWT 一般有 HEADER (頭),PAYLOAD (有效載荷)和 SIGNATURE (簽名)三個部分組成,三者之間使用「.」連接,格式如上圖所示。
JWT 的缺陷
JWT 標準設計看起來很不錯,實際上包藏禍心,由於其設計,隱藏諸多安全問題,詳細以下:
重置空加密算法缺陷
JWT 支持將算法設定爲「None」,若是你使用JWT庫時未設置關閉該功能,那麼任何Token都是有效的。具體作法是將JWT第一部分 header 中 alg 字段設置爲 None ,再將第三部分 signature 設置爲空(即不添加signature字段), 此token 可順利經過驗證。
密鑰混淆攻擊
JWT 最經常使用的兩種算法是 HMAC 和 RSA。HMAC(對稱加密算法)用同一個密鑰對 token 進行簽名和認證。而RSA(非對稱加密算法)須要兩個密鑰,先用私鑰加密生成 JWT ,而後使用其對應的公鑰來解密驗證。若是公鑰泄露(不少人以爲公鑰能夠分發),那麼將算法 RS256 修改成 HS256 ,即非對稱加密向降低級爲對稱加密。再使用這個泄露的公鑰對 token 進行簽名,後端收到後根據頭中指定的算法,將使用公鑰對 token 驗證,如此便認證經過。
密鑰暴力破解
若是 JWT 使用對稱加密算法(如 HS256), 這意味着對令牌進行簽名的密鑰也用於對其進行驗證。因爲簽名驗證是一個自包含的過程,所以能夠測試 token 自己的有效密鑰,而沒必要將其發送迴應用程序進行驗證。若是密鑰設置過於簡單,如經常使用詞彙、生日年份等,結合已知的泄露密碼列表,將很快破解出密鑰,如此即可僞造出任意token 。
kid 指定攻擊
kid 即爲 key ID ,存在於 jwt header 中,是一個可選的字段,用來指定加密算法的密鑰。如在頭部注入新的 kid 字段,並指定 HS256 算法的 key 爲 123456,生成新的token,服務端收到將使用指定的密鑰123456來驗證token。
如何避免 JWT 的缺陷
以上列舉的問題中,除了密鑰暴力破解,其他皆爲 jwt 標準設計引起的缺陷。若是你選擇使用 jwt 標準,那麼請找一個靠譜的實現庫,並進行安全測試。請避免使用對稱加密算法,並正確配置安全項,如開啓驗證 jwt 頭部,禁止 alg 設置爲 none,禁止密鑰降級等安全措施。不過最好的避免方式就是不用 jwt ,改用 paseto ,一個替代 jwt 的新標準。
JWT 進行會話管理的「優勢」
首先,如前文所述,JWT 標準並不是是爲會話管理而設計,固然 paseto 標準( JWT 標準的替代者)也不是。現現在大多數使用 JWT 進行會話管理其實是 Client Side Session 模式。前文講到 Client Side Session 模式將會話數據存儲到客戶端,那麼爲了防止數據篡改或者泄露,通常對存儲在客戶端的數據進行簽名或加密。不少人就利用 JWT 實現庫將會話數據放在 PAYLOAD 區域,而後生成一個token,發送給客戶端。客戶端若是是網頁瀏覽器,甚至經過 js 將收到的 token 放置在 localStorage 中。以上操做讓開發者感受一切盡在掌握之中,卻不知集萬千漏洞於一身。
使用 JWT 進行會話管理的人聲稱優勢以下:
- 自然支持分佈式驗證;
- 高併發降低低服務器壓力;
- 靈活易用;
- 更安全,可防止CSRF;
- 在移動設備上效果更好;
- 適用於阻止 cookie 的用戶。
以上聲稱的優勢其實是 Client Side Session 模式和自主控制 token 帶來的。可是我要告訴各位,對於使用者來講,以上優勢所有是僞命題。
自然支持分佈式驗證
這個特色是真的,然而現實狀況是幾乎沒有人真正須要這個特色。由於 session 共享技術很成熟,現有軟件框架都提供了良好的支持方案。
高併發降低低服務器壓力
看上去高併發下,Client Side Session 模式沒有服務端存儲和查詢,可下降服務器壓力。然而,若是真的是高併發,更應該考慮業務處理對服務器帶來的壓力,而不是會話存儲和查詢,由於這根本就是九牛一毛。
靈活易用
這徹底是個錯誤,自定義使用 JWT 進行會話管理,須要更多的代碼配置來處理,何來靈活易用?
更安全,可防止CSRF
前文介紹 JWT 標準,已說明 JWT 存在不少設計問題,更沒必要說 Client Side Session 模式帶來信息泄露的風險。而對於能夠防止CSRF,除非你把 token 放在 cookies 之外區域,如 localStorage 中,使用JS來操做,然而這將帶來更大的風險。
在移動設備上效果更好
好久之前,部分移動瀏覽器存在不支持cookie的狀況,但這已是過去式了。如今但凡是個靠譜的 HTTP 庫,移動開發框架都支持 cookie,因此這根本不是個問題。
適用於阻止 cookie 的用戶
確實有用戶會阻止 cookie 的使用,爲了不被跟蹤。但創建會話就是要求登陸驗證身份信息,因此,選擇阻止 cookie 使用的用戶明白,爲何我登陸不了。對於真心想避免跟蹤的用戶,不只僅會阻止cookie,而是會阻止一切客戶端存儲。這樣,Client Side Session 模式下,會話信息也是無處可存。
若是你偏心 Client Side Session 模式,並且確實以爲 Client Side Session 模式能夠知足你的需求,那僅需使用服務端框架支持的 Client Side Session 模式實現便可,而沒必要去使用 JWT 。
HTTP 會話管理的正確之道
對於安全會話,首先是須要使用 HTTPS,用於保證傳輸通道的安全。而後使用 Server Side Session 模式的實現,如網頁瀏覽器中的 cookie 存儲隨機標識符,即 session-id,該 session-id 與服務端存儲的 session-data 配對。若是你須要保持更久的會話,可加入一個長期的 remember me id 來實現,而不是隨意延長 session-id 的有效期。
有朋友說,JWT 會話管理可經過擴展避免 Client Side Session 模式下的缺點。如吊銷會話,服務端可加入黑名單機制,或者服務端記錄簽發出去的 token,並設置有效期,而後每次請求進行比對。那麼問題來了,這樣仍是 Client Side Session 模式嗎?
補充
有朋友說 JWT 很是簡單的實現了單點登陸,一問究竟,我竟無言以對。原來他是指多個系統配置同樣的密鑰進行token驗證,便可實現系統間無縫身份驗證。沒錯,這其實就是 Client Side Session 模式的優勢,在特定場景下可實現「單點登陸」的效果。但現實大多數場景下是不可能達成的,由於首先須要各個不一樣公司開發的軟件系統都採用 JWT 進行會話管理,接着互相信任共享同一個密鑰(這是極其不安全的)。如今主流解決單點登陸的協議是OIDC 或者是 OAuth2,再古老些也是 SAML 或 CAS。
有朋友問,既然 JWT 並非爲會話管理設計的,那 JWT 應該用來作什麼?JWT 適用於一次性令牌,即短有效性,一次性使用。如用於密碼找回郵件中的短效一次性連接,或者是文件下載。
參考文獻
[1] [EB/OL].http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/.2021-05-04
[2] paseto [EB/OL].https://github.com/paragonie/paseto/.2021-05-04
[3] [EB/OL].https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid.2021-05-04
[4] [EB/OL].https://geekkeen.github.io/http-cookie-session.html.2021-05-04
[5] [EB/OL].https://blog.by24.cn/archives/about-session.html.2021-05-04