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