一步步帶你了解前後端分離利器之JWT
一、HTTP的無狀態性
HTTP 是無狀態協議,它不對之前發送過的請求和響應的狀態進行管理。也就是說,無法根據之前的狀態進行本次的請求處理。假設要求登錄認證的 Web 頁面本身無法進行狀態的管理(不記錄已登錄的狀態),那麼每次跳轉新頁面不是要再次登錄,就是要在每次請求報文中附加參數來管理登錄狀態。
不可否認,無狀態協議當然也有它的優點。由於不必保存狀態,自然可減少伺服器的 CPU 及內存資源的消耗。從另一側面來說,也正是因為 HTTP 協議本身是非常簡單的,所以才會被應用在各種場景里。
二、Cookie 技術的引入
如果讓伺服器管理全部客戶端狀態則會成為負擔,保留無狀態協議這個特徵的同時又要解決類似的矛盾問題,於是引入了 Cookie 技術。Cookie 技術通過在請求和響應報文中寫入Cookie信息來控制客戶端的狀態。
Cookie會根據從伺服器端發送的響應報文內的一個叫做Set-Cookie 的首部欄位信息,通知客戶端保存 Cookie。當下次客戶端再往該伺服器發送請求時,客戶端會自動在請求報文中加入Cookie 值後發送出去。
1、沒有 Cookie 信息狀態下的請求(圖片來源《圖解HTTP》)
2、第 2 次以後(存有 Cookie 信息狀態) 的請求(圖片來源《圖解HTTP》)
3、詳細介紹Cookie 傳輸過程
伺服器端發現客戶端發送過來的 Cookie 後, 會去檢查究竟是從哪一個客戶端發來的連接請求, 然後對比伺服器上的記錄, 最後得到之前的狀態信息。
三、基於表單的認證
目前用戶的認證多半是基於表單的認證,基於表單的認證一般會使用 Cookie 來管理Session(Session會話,Session代表著伺服器和客戶端一次會話的過程,直到Session失效(服務端關閉)或者客戶端關閉時結束)。基於表單認證本身是通過伺服器端的 Web應用,將客戶端發送過來的用戶ID和密碼與之前登錄過的信息做匹配來進行認證的。
但鑒於 HTTP 是無狀態協議, 之前已認證成功的用戶狀態無法通過協議層面保存下來。 即無法實現狀態管理, 因此即使當該用戶下一次繼續訪問,也無法區分他與其他的用戶。於是我們會使用Cookie 來管理 Session,以彌補 HTTP 協議中不存在的狀態管理功能。
簡單的來說就是,用戶在登錄的時候,會在Web伺服器中開闢一段內存空間Session用於保存用戶的認證信息和其他信息,用戶登錄成功之後會通過Set-Cookie的首部欄位信息,通知客戶端保存Cookie,而這Cookie保存的就是伺服器端Session的ID,下次請求的時候客戶端會帶上該Cookie向伺服器端發送請求,伺服器端進行校驗,如果Session中保存的有該ID的Session就表示用戶認證通過,否則失敗!
四、Session存儲位置以及集群情況下的問題
Session 是存儲在Web伺服器(例如:Tomcat)中的,並針對每個客戶端(客戶),通過SessionID來區別不同用戶的。Session是以Cookie技術或URL重寫實現,默認以Cookie技術實現,服務端會給這次會話創造一個JSESSIONID的Cookie值。
但是一個顯著的問題就是,在集群模式下如果通過Nginx負載均衡的時候,如果有一個用戶登錄的時候請求被分配到伺服器A上,登錄成功後設置的Session就會存放在伺服器A上了,但是在伺服器B上卻沒有該用戶的Session數據,當用戶再次發起一個請求的時候,此時請求如果被分配到伺服器B上,則就不會查詢到該用戶的登錄狀態,就會出現登錄失敗的情況!
一種可以想到的方式就是將多個Web伺服器上存儲的Session統一存儲到某一存儲介質中,保證進集群中的每一台機器都可以看到所有相同Session數據,這裡的同步體現在所有的Session存儲在同一的存儲介質裡邊。
幸運的是我們常用的Tomcat容器已經為我們提供了一個介面,可以讓我們實現將Session存儲到除當前伺服器之外的其他存儲介質上,例如Redis等。
了解Spring Session的小夥伴可能都會知道Spring Session的本質就是通過實現Tomcat提供的該介面將Session存儲到Redis中,以此來實現Session的統一存儲管理,對Spring Session有興趣的小夥伴可以參考往期的文章:
五、小結與需求痛點
Session和Cookie的目的相同,都是為了克服HTTP協議無狀態的缺陷,但完成的方法不同。Session通過Cookie,在客戶端保存SessionID,而將用戶的其他會話消息保存在服務端的Session對象中,與此相對的,Cookie需要將所有信息都保存在客戶端。因此Cookie存在著一定的安全隱患,例如本地Cookie中保存的用戶名密碼被破譯,或Cookie被其他網站收集,例如:
上述過程我們簡單的描述了Session的演進過程還有使用同步的方式解決Session在集群的時候出現的問題,但是我們意識到了使用Spring Session的方式來實現Session的同步是一件相對比較麻煩的事情,我們雖然使用Redis來進行同步,但是Redis並不是100%可靠的,我們需要對Redis搭建集群、進行主從同步複製、進行持久化等,顯然這是一件很複雜的事情,因此有沒有一種小而輕便的方式來實現我們的這種認證需求!那就是JWT了!
除了上述我們遇到的問題之外,在目前前後端分離的大環境下經常會遇到需要根據用戶來分配許可權和顯示相對應信息的問題,雖然傳統的Cookie和Session機制可以解決這個問題,但就通用性而言,JWT(JSON Web Token)相對來說更好。
看到這裡很多小夥伴都已經按捺不住了!那JWT到底是什麼呢?
六、JWT是什麼
Json web token (JWT),是為了在網路應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519)。該標準被設計為緊湊且安全的,一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息。當然該標準也可直接被用於認證,也可被加密。
JWT的幾個特點:
1、由於它們的尺寸較小,JWT可以通過URL,POST參數或HTTP頭部發送。 另外,尺寸越小意味著傳輸速度越快。
2、有效載荷包含有關用戶的所有必需信息,避免了多次查詢資料庫的需要。
JWT的使用場景:
1、驗證
這是使用JWT最常見的情況。 一旦用戶登錄,每個後續請求將包括JWT。它將允許用戶訪問該令牌允許的路由,服務和資源。 單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小,而且能夠輕鬆地跨不同域使用。
2、信息交換
JWT是在各方之間安全傳輸信息的好方法, 因為JWT可以被簽名(例如使用公鑰/私鑰對進行簽名)。所以你可以確定發件人是他們說的那個人。 此外,由於使用頭部(header)和有效載荷(payload)計算簽名,因此您還可以驗證內容是否未被篡改。
七、JWT的結構說明
JWT包含三個由點(.)分隔的部分,它們是:
因此,JWT通常看起來如下所示:
1、頭部(header)
頭部(header)通常由兩部分組成:令牌的類型(即JWT)和正在使用的散列演算法(如HMAC SHA256或RSA)。如下所示:
然後,將這個JSON用Base64編碼,形成JWT的第一部分。
2、有效負載(payload)
令牌的第二部分是包含聲明的有效載荷。 聲明是關於實體(通常是用戶)和附加元數據的聲明。 有三種類型的聲明:
(1)標準中註冊的聲明:這是一組預先定義的聲明,這些聲明不是強制性的,但建議提供一套有用的,可互操作的聲明。 如下:
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什麼時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而迴避重放攻擊。
注意:聲明名稱只有三個字元長,因為JWT是緊湊的。
(2)公開聲明:這些可以由使用JWT的人員隨意定義。 但為避免衝突,應在IANA JSON Web令牌註冊表中定義它們,或者將其定義為包含防衝突命名空間的URI。
(3)私人聲明:這是為了共享使用它們的當事方之間共享信息而創建的聲明,既不是登記聲明,也不是公開聲明。
示例如下:
然後將有效載荷進行Base64編碼,以形成JSON Web令牌的第二部分。
3、簽名(signature)
要創建簽名部分,您必須採用頭部(header),有效載荷(payload),密鑰(secret),以及頭部中指定的演算法。例如,如果你想使用HMAC SHA256演算法,簽名將按以下方式創建:
簽名通常用於驗證JWT的發件人是誰,並JWT在傳送的過程中不被篡改。
注意:上圖紅框中的secret是保存在伺服器端的,JWT的簽發生成也是在伺服器端的,secret就是用來進行JWT的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret,那就意味著客戶端是可以自我簽發jwt了。
4、案例演示
下面顯示了一個登錄請求成功之後服務端返回的Token,它由編碼頭部(header)、編碼有效載荷(payload)和簽名(signature)通過(.)拼接而成:
如果需要,你可以使用jwt.io的Debugger工具,來編碼、驗證和生成JWT。操作界面如下:
八、JWT的工作原理
在身份驗證中,當用戶使用他們的憑證(如用戶名、密碼)成功登錄時,後台伺服器將返回一個token,前端接收到這個token將其保存在本地(通常在本地存儲中,也可以使用Cookie,但不是傳統方法中創建會話,伺服器並返回一個cookie)。下次用戶想要訪問受保護的路由或資源時,就將本地保存的token放在頭部Header中發送到後台伺服器。伺服器接收到請求,檢查頭部中token的存在,如果存在就允許訪問受保護的路由或資源,否則就不允許。如下所示:
一般默認的Value是以「Bearer 」開始,注意這裡的Bearer之後有一個空格,以便後端進行分割。
這是一種無狀態身份驗證機制,因為用戶狀態永遠不會保存在伺服器內存中。 由於JWT是獨立的,所有必要的信息都在那裡,所以減少了多次查詢資料庫的需求。
九、總結
1、優點
(1)因為Json的通用性,所以JWT是可以進行跨語言支持的,像Java、JavaScript、NodeJS、PHP等很多語言都可以使用。
(2)因為有了payload部分,所以JWT可以在自身存儲一些其他業務邏輯所必要的非敏感信息。
(3)便於傳輸,JWT的構成非常簡單,位元組佔用很小,所以它是非常便於傳輸的。
(4)它不需要在服務端保存會話信息, 所以它易於應用的擴展
2、安全相關
(1)不應該在JWT的payload部分存放敏感信息,因為該部分是客戶端可解密的部分。
(2)保護好secret私鑰,該私鑰非常重要。
(3)如果可以,請使用HTTPS協議,不!是務必使用HTTPS!
十、文末彩蛋
後續會有兩至三篇文章介紹JWT的使用和JWT的優缺點以及如何保證token的安全性等,敬請期待!
參考文章:
1、https://www.bysocket.com/?p=362
2、https://www.bysocket.com/?p=384
3、伺服器前後端分離之JWT用戶認證
4、部分截圖和內容參考《圖解HTTP》
TAG:Java後端技術 |