現在,JSON Web Tokens (JWT) 是非常流行的。尤其是 Web 開發領域。
- 流行
- 安全
- 穩定
- 易用
- 支援 JSON
所有這些因素,令 JWT 名聲大振。
但是,今天我要來說說使用 JWT 的缺點。也就是為什麼說將 JWT 用於會話控制是多麼的糟糕。
為什麼使用 JWT?
如果你不瞭解 JWT,不要緊張,它並不可怕。
JWT 只是用於網路間傳遞宣告而執行一種基於 JSON 的標準。
例如,我是個盲人,而且聽力也不好。你上週幫我買了午餐,現在我需要你的收款賬號,把錢還給你。如果我詢問你的賬號,但是其他人高呼他們的賬號,由於我把別人的賬號誤認為是你的,我可能會不小心把錢打給別人。
JWT 旨在防止這種情況發生。JWT 提供了一種簡單的方法,在彼此傳遞資料時,驗證是由誰先建立了資料。
所以,像上述的例子,即使我收到了超過 100 萬個 JWT 返回的賬號資訊,我也很容易可以辨別出來哪個真實來自於你。
JWT 如何執行?
JWT 是 JSON 格式的被加密了的字串。
JWT 的核心是金鑰,就是 JSON 資料。這是你關心的,並希望安全傳遞出去的資料。JWT 如何做到這一點,並使你信任它,就是加密簽名。
比如說,我寫了一封信,當我署名這封信時,意味著只要讀過這封信的人,都知道是我寫了這封信。而且我的簽名是獨一無二的,所以不會被懷疑真實性。加密簽名的方式大致相同,JWT 有兩種加密方式:對稱加密和非對稱加密,兩種方式有同等的效用。
JWT 內容加密
其實,JWT 的內容(內部的 JSON 資料)通常是不加密的。這意味著,即使沒有金鑰,也可以檢視 JWT 內的資料。JWT 預設並不會加密你的資料,它只是幫助你驗證是你信任的一方建立了它。
如果你確實需要加密 JWT 內容,可以使用 JWE 進行加密,但這種做法並不常見。請確保使用了正確的方法。
如今人們是如何使用 JWT 的?
JWT 最常見的用途是身份驗證。有大量的 Web 安全庫使用 JWT 建立會話控制,API 令牌等。
這種做法通常是,當需要對網站/ API 進行身份驗證時,伺服器生成一個包含使用者 ID 的 JWT,以及其它一些關鍵性的資訊,然後再將其傳送給瀏覽器/客戶端等,儲存為會話令牌。
例如,當使用者訪問網站上的另一個頁面時,瀏覽器會自動將該 JWT 傳送到伺服器,伺服器驗證 JWT 確認和最初建立的令牌相同,然後允許使用者執行後續的操作。
從理論上看,還不錯,因為:
- 當伺服器收到 JWT 時,可以驗證其是否是合法的,是否是信任使用者的令牌
- 可以在伺服器本地驗證,而不需要任何其它的網路請求,與資料庫的通訊等。這可能令管理會話更高效,因為無需從資料庫(快取)載入使用者資訊,只需要在本地執行一小部分程式碼。這可能是人們喜歡用 JWT 的最大原因。
似乎很棒,既可以提高 Web 應用的效能,又可以減少快取伺服器和資料庫伺服器的負載,提供更好的體驗。另外你還可以在 JWT 中儲存使用者許可權資訊、使用者個人資訊等等更多的額外資訊進一步減少資料庫壓力。
為什麼 JWT 不是最好的會話令牌?
我們已經瞭解了 JWT 如何用於身份驗證,讓我們進入本篇的中心話題:為什麼 JWT 不是最好的會話令牌,為什麼普通的舊會話方式在幾乎各方面都優於 JWT。
背景
我們先了解一些背景知識。開發人員構建的大多數網站都相對比較簡單:
- 使用者註冊
- 使用者登入
- 使用者點選執行操作
- 網站使用使用者資訊進行建立、刪除、更新、查閱資訊
對於這類網站,要知道使用者進行互動的每個頁面都會包含某些動態資料。比如:你正在訪問一個需要使用者登入才能進一步操作的網站,你經常會在資料庫中對使用者進行這些操作:
- 記錄使用者正在執行的操作
- 將使用者的某些資訊新增到資料庫中
- 檢查使用者的許可權看其是否可以執行某項操作
- 等等
資料
我們來看兩種方案:
- 在 Cookie 中儲存使用者 ID(abc123)
- 在 JWT 中儲存使用者 ID(abc123)
如果我們將 ID 儲存在 Cookie 中,需要 6 個位元組。如果將 ID 儲存在 JWT 中(設定基本的請求頭欄位,以及一些其它資訊),需要幾百位元組甚至更多。對於簡單的會話控制,每個頁面的請求就增大了幾十倍。
假如你的網站每月有 10 萬次的瀏覽器,就意味著要多開銷幾十兆的流量。聽起來並不多,但日積月累也是不小一筆開銷。實際上,許多人會在 JWT 中儲存的資訊會更多。
無論如何你需要運算元據庫
如上所述,大多數需要使用者登入的網站主要是 CRUD 操作(增查改刪)生成動態內容。
在網站上使用 JWT,對於使用者載入的幾乎所有頁面,都需要從快取/資料庫中載入使用者資訊,可能出現下列情況:
- 需要使用者關鍵性資訊查詢(例如:判斷使用者賬號是否有足夠的資金完成交易?)
- 需要將一些資訊儲存進資料庫(例如:使用者相關的唯一資訊,需要根據該資訊對使用者進行檢索)
- 必須從快取/資料庫中查詢完整的資訊,方便網站生成完整的動態頁面內容。
想想你的網站是否會遇到上述情形。這意味著大多數網站不適用 JWT 的無狀態特性。為了解決這個問題,就需要 JWT 變得更大,而且需要使用 CPU 來計算簽名,就會導致比傳統會話慢許多。
其實,幾乎每個 Web 框架支援在每次請求傳入使用者資訊,這包括 Django,Rails,Express.js 等(如果有用到身份驗證功能)。另外,如果你使用 Memcached/Redis 等快取伺服器對使用者資訊進行快取,檢索會變得非常快。
多餘的簽名
JWT 的賣點之一就是加密簽名,由於這個特性,接收方得以驗證 JWT 是否有效且被信任。
但是,其實在過去 20 年中幾乎每一個框架對於普通會話 Cookie 都可以獲得很好的加密簽名處理。這意味著你可以獲得與 JWT 完全一致的效果,況且大多數 Web 身份認證應用中,JWT 都會被儲存到 Cookie 中,這就是說你有了兩個層面的簽名。
聽著似乎很贊,但是沒有任何優勢,為此,你需要花費兩倍的 CPU 開銷來驗證簽名。對於有著嚴格效能要求的 Web 應用,這並不理想,尤其對於單執行緒環境。
更好的解決方案是什麼?
如果你正在構建上述型別的網站,那麼最好選擇舊的,簡單且安全的伺服器端會話。而不是將使用者 ID 儲存到 JWT 中,然後再將 JWT 儲存到 Cooike 中。只需將使用者 ID 直接儲存到 Cookie 中即可。
如果你的網站很受歡迎,有著大的訪問量,可以將會話快取到 Memcached/Redis,同時也有利於擴充套件你的服務。
什麼時候使用 JWT?
JWT 雖然對於大多數網站都沒有用,但是有幾種情況它是很有用的。
如果你正在構建從伺服器到伺服器或客戶端到伺服器(如:移動應用 APP 或單頁面應用)的 API 服務,那麼使用 JWT 是非常明智的。比如:
- 你的客戶端需要通過 API 進行身份驗證,並返回 JWT
- 然後,客戶端使用返回的 JWT 經過身份驗證去請求其它的 API 服務
- 這些其它 API 服務通過客戶端的 JWT 驗證客戶端是可信的,並且可以執行某些操作無需再次驗證
對於這類 API 服務,JWT 非常適合,因為客戶端需要頻繁進行請求,並且許可權是可控的,通常認證資料以無狀態方式持久存在,不需要過多依賴使用者資訊。
如果你正在構建的應用類似單點登入或 OpenID Connect 認證,JWT 同樣十分適合,就是實現一種通過第三方驗證使用者的方法。
總結
當你準備構建下一個網站時,只需要使用 Web 框架預設的身份認證功能即可,不需要再整合 JWT 方式。
關注公眾號「展白說」,獲取更多有價值的資訊。