無(wú)狀態(tài)協(xié)議
什么是無(wú)狀態(tài)協(xié)議?
無(wú)狀態(tài)是指協(xié)議對(duì)于事務(wù)處理沒(méi)有記憶功能,缺少狀態(tài)意味著,如果后面的處理需要用到前面的信息,則前面的信息則需要重新再傳,這樣可能導(dǎo)致每次連接傳送的數(shù)據(jù)量增大。另一方面,在服務(wù)器不需要前面信息時(shí),應(yīng)答較快,直觀來(lái)講,就是,每個(gè)請(qǐng)求都是獨(dú)立的,與前面的請(qǐng)求和后面的請(qǐng)求都是沒(méi)有直接聯(lián)系的。
實(shí)際中的使用情況
在web應(yīng)用中,我們使用HTTP協(xié)議,但是我們需要的web是有狀態(tài)的,因此加入了cookie,session等機(jī)制實(shí)現(xiàn)有狀態(tài)的web,
web = http協(xié)議 + 狀態(tài)機(jī)制 + 其他機(jī)制
為什么不改進(jìn)http協(xié)議使之有狀態(tài)?
最初的http協(xié)議只是用來(lái)瀏覽靜態(tài)文件的,無(wú)狀態(tài)協(xié)議已經(jīng)足夠,這樣實(shí)現(xiàn)的負(fù)擔(dān)也很輕(相對(duì)來(lái)說(shuō),實(shí)現(xiàn)有狀態(tài)的代價(jià)是很高的,要維護(hù)狀態(tài),根據(jù)狀態(tài)來(lái)操作。)。隨著web的發(fā)展,它需要變得有狀態(tài),但是不是就要修改http協(xié)議使之有狀態(tài)呢?是不需要的。因?yàn)槲覀兘?jīng)常長(zhǎng)時(shí)間逗留在某一個(gè)網(wǎng)頁(yè),然后才進(jìn)入到另一個(gè)網(wǎng)頁(yè),如果在這兩個(gè)頁(yè)面之間維持狀態(tài),代價(jià)是很高的。其次,歷史讓http無(wú)狀態(tài),但是現(xiàn)在對(duì)http提出了新的要求,按照軟件領(lǐng)域的通常做法是,保留歷史經(jīng)驗(yàn),在http協(xié)議上再加上一層實(shí)現(xiàn)我們的目的(“再加上一層,你可以做任何事”)。所以引入了其他機(jī)制來(lái)實(shí)現(xiàn)這種有狀態(tài)的連接。
哪些方法可以實(shí)現(xiàn)有狀態(tài)連接
cookie,session,application
有人將web應(yīng)用中有無(wú)狀態(tài)的情況,比著顧客逛商店的情景。
顧客:瀏覽器訪問(wèn)方;
商店:web服務(wù)器;
一次購(gòu)買:一次http訪問(wèn)
我們知道,上一次顧客購(gòu)買,并不代表顧客下一個(gè)小時(shí)一定會(huì)買(當(dāng)然也不能代表不會(huì))。也就是說(shuō)同一個(gè)顧客的不同購(gòu)買之間的關(guān)系是不定的。所以說(shuō)實(shí)在的,這種情況下,讓商店保存所有的顧客購(gòu)買的信息,等到下一次購(gòu)買可以知道這個(gè)顧客以前購(gòu)買的內(nèi)容代價(jià)非常大的。所以商店為了避免這個(gè)代價(jià),索性就認(rèn)為每次的購(gòu)買都是一次獨(dú)立的新的購(gòu)買。淺臺(tái)詞:商店不區(qū)分對(duì)待老顧客和新過(guò)客。這就是無(wú)狀態(tài)的。
但是,商店為了提高收益。她是想鼓勵(lì)顧客購(gòu)買的。所以告訴你,只要你在一個(gè)月內(nèi)購(gòu)買了5瓶以上的啤酒,就送你一個(gè)酒杯。
我們看看這種情況我們?cè)趺慈?shí)現(xiàn)呢?
A,給顧客發(fā)放一個(gè)磁卡,里面放有顧客過(guò)去的購(gòu)買信息。
這樣商店就可以知道了。這就是cookie.
B,給顧客發(fā)放一個(gè)唯一號(hào)碼,號(hào)碼制定的顧客的消費(fèi)信息,存儲(chǔ)在商店的服務(wù)器中。這就是session。
最后,商店可以全局的決定,是5瓶為送酒杯還是6瓶。這就是application。
其實(shí),這些機(jī)制都是在無(wú)狀態(tài)的傳統(tǒng)購(gòu)買過(guò)程中加入了一點(diǎn)東西,使整個(gè)過(guò)程變得有狀態(tài)。Web應(yīng)用就是這樣的。
什么是JWT?
Json Web Token
是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn),該token被設(shè)計(jì)為緊湊而安全的,特別適用于分布式站點(diǎn)的單點(diǎn)登錄(SSO)場(chǎng)景。JWT的聲明一般被用來(lái)在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源,也可以增加一些額外的其他業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認(rèn)證,也可以被加密。
起源
先說(shuō)一下,基于token認(rèn)證與傳統(tǒng)的session認(rèn)證有什么區(qū)別
傳統(tǒng)的session認(rèn)證
由上面我們可以知道http協(xié)議本身是屬于無(wú)狀態(tài)協(xié)議,這就意味著,如果用戶向我們提供用戶名和密碼來(lái)進(jìn)行用戶認(rèn)證,那么下一次請(qǐng)求時(shí),用戶還是要再進(jìn)行一次用戶認(rèn)證才行,因?yàn)楦鶕?jù)http協(xié)議,我們并不知道是哪個(gè)用戶發(fā)出的請(qǐng)求,so,為了讓我們的應(yīng)用服務(wù)能夠識(shí)別出是哪個(gè)用戶發(fā)出的請(qǐng)求,我們只能在服務(wù)器存儲(chǔ)一份用戶的登錄信息,這份用戶的登錄信息會(huì)在服務(wù)器響應(yīng)請(qǐng)求的時(shí)候返回給客戶端(傳遞給瀏覽器),告訴其保存為cookie,以便于下次請(qǐng)求時(shí)發(fā)送給我們的應(yīng)用服務(wù),這樣我們的應(yīng)用就能識(shí)別出是哪個(gè)用戶發(fā)出的請(qǐng)求了。這就是傳統(tǒng)的基于session認(rèn)證。
但是這種基于session的認(rèn)證應(yīng)用本身很難得到擴(kuò)展,隨著不同客戶端的增加,獨(dú)立的服務(wù)器就無(wú)法承載更多的用戶,這時(shí)候,問(wèn)題就會(huì)暴露出來(lái)。
基于session認(rèn)證暴露出的問(wèn)題
session:每個(gè)用戶經(jīng)過(guò)服務(wù)端認(rèn)證之后,都要在服務(wù)端做一次記錄,以便用戶的下一次請(qǐng)求的鑒別,通常session都是保存在內(nèi)存當(dāng)中,而隨著認(rèn)證用戶的增加,服務(wù)端的開銷會(huì)明顯增大
擴(kuò)展性:用戶認(rèn)證之后,服務(wù)端做記錄,如果認(rèn)證信息是保存在內(nèi)存當(dāng)中的話,這就意味用戶的下次請(qǐng)求必須還是在這臺(tái)服務(wù)機(jī)器上才能拿到授權(quán)的資源,相應(yīng)的也就限制了負(fù)載均衡的能力,這也就限制了擴(kuò)展的能力。
CSRF:因?yàn)槭腔赾ookie來(lái)進(jìn)行用戶識(shí)別的,cookie如果被截獲,用戶很容易受到跨站請(qǐng)求偽造的攻擊。
基于token的鑒權(quán)機(jī)制
基于token的鑒權(quán)機(jī)制類似于http協(xié)議也是無(wú)狀態(tài)的,他不需要在服務(wù)端保留用戶的認(rèn)證信息或者會(huì)話信息。這就意味著基于token的認(rèn)證機(jī)制的應(yīng)用不需要考慮用戶在哪一臺(tái)服務(wù)器登錄了,這就為應(yīng)用的擴(kuò)展提供了便利。
流程上是這樣的:
- 用戶使用用戶名和密碼來(lái)請(qǐng)求服務(wù)器
- 服務(wù)器來(lái)進(jìn)行驗(yàn)證用戶的信息
- 服務(wù)器通過(guò)驗(yàn)證了,發(fā)送給用戶一個(gè)token
- 客戶端存儲(chǔ)token,并在每次請(qǐng)求時(shí)附送這個(gè)token值
- 服務(wù)端驗(yàn)證token值,并返回?cái)?shù)據(jù)
這個(gè)token必須要在每次請(qǐng)求時(shí)傳遞給服務(wù)端,它應(yīng)該保存在請(qǐng)求頭里,另外,服務(wù)端要支持CORS(跨來(lái)源資源共享)策略,一般我們?cè)诜?wù)端這么做就可以了
Access-Control-Allow-Origin: *
那么現(xiàn)在我們回到JWT主題上
JWT長(zhǎng)什么樣?
jwt是由三段信息構(gòu)成的,將這三段信息用.號(hào)連接一起就構(gòu)成了JWT串。like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT的構(gòu)成
- 第一部分:header 頭部
- 第二部分:payload 載荷(類似于飛機(jī)上承載的物品)
- 第三部分:signature 簽證
header
JWT的頭部承載兩部分信息:
- 聲明類型,這里是jwt
- 聲明加密的算法,通常是直接使用 HMAC SHA256
{
'typ': 'JWT',
'alg': 'HS256'
}
然后將頭部進(jìn)行base64加密(該加密是可以對(duì)稱解密的),構(gòu)成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload
載荷就是存放有效信息的地方。這個(gè)名字就像飛機(jī)上承載的物品,這些有效信息包含三個(gè)部分:
- 標(biāo)準(zhǔn)中注冊(cè)的聲明
- 公共的聲明
- 私有的聲明
標(biāo)準(zhǔn)中注冊(cè)的聲明(建議但不強(qiáng)制使用):
- iss:jwt簽發(fā)者
- sub:jwt所面向的用戶
- 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,從而回避重放攻擊。
公共的聲明:
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需求的必要信息,但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷?/p>
私有的聲明:
私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)閎ase64是對(duì)稱解密的,意味著該部分信息可以歸類為明文信息。
定義一個(gè)payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后將其進(jìn)行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
jwt的第三部分是一個(gè)簽證信息,這個(gè)簽證信息是由三部分組成:
- header (base64后的)
- payload (base64后的)
- secret
這個(gè)部分需要base64加密后的header和加密后的payload使用.號(hào)連接組成的字符串,然后通過(guò)header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
得到結(jié)果:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服務(wù)端的,jwt的簽發(fā)生成也是在服務(wù)器端的,secret就是用來(lái)進(jìn)行jwt的簽發(fā)和jwt的驗(yàn)證,所以,它就是你服務(wù)端的私鑰,在任何場(chǎng)景都不應(yīng)該流露出去,一旦客戶端得知這個(gè)secret,那就意味著客戶端可以自我簽發(fā)jwt了
如何應(yīng)用?
一般是在請(qǐng)求頭里加入Authorization,并加上Bearer標(biāo)注:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服務(wù)端會(huì)驗(yàn)證token,如果驗(yàn)證通過(guò)就會(huì)返回相應(yīng)的資源。整個(gè)流程就是這樣的:

總結(jié)
優(yōu)點(diǎn)
- 因?yàn)閖son的通用性,所以JWT是可以進(jìn)行跨語(yǔ)言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語(yǔ)言都可以使用。
- 因?yàn)橛辛藀ayload部分,所以JWT可以在自身存儲(chǔ)一些其他業(yè)務(wù)邏輯所必要的非敏感信息。
- 便于傳輸,jwt的構(gòu)成非常簡(jiǎn)單,字節(jié)占用很小,所以它是非常便于傳輸?shù)摹?/li>
- 它不需要在服務(wù)端保存會(huì)話信息, 所以它易于應(yīng)用的擴(kuò)展
安全相關(guān)
- 不應(yīng)該在jwt的payload部分存放敏感信息,因?yàn)樵摬糠质强蛻舳丝山饷艿牟糠帧?/li>
- 保護(hù)好secret私鑰,該私鑰非常重要。
如果可以,請(qǐng)使用https協(xié)議