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