什么是JWT
Json web token (JWT), 是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)((RFC 7519).該token被設(shè)計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息哑舒,以便于從資源服務(wù)器獲取資源植榕,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息粱腻,該token也可直接被用于認(rèn)證庇配,也可被加密。
起源
說起JWT,我們應(yīng)該來談一談基于token的認(rèn)證和傳統(tǒng)的session認(rèn)證的區(qū)別。
傳統(tǒng)的session認(rèn)證
我們知道甘凭,http協(xié)議本身是一種無狀態(tài)的協(xié)議,而這就意味著如果用戶向我們的應(yīng)用提供了用戶名和密碼來進(jìn)行用戶認(rèn)證啸澡,那么下一次請求時,用戶還要再一次進(jìn)行用戶認(rèn)證才行氮帐,因為根據(jù)http協(xié)議嗅虏,我們并不能知道是哪個用戶發(fā)出的請求,所以為了讓我們的應(yīng)用能識別是哪個用戶發(fā)出的請求上沐,我們只能在服務(wù)器存儲一份用戶登錄的信息皮服,這份登錄信息會在響應(yīng)時傳遞給瀏覽器,告訴其保存為cookie,以便下次請求時發(fā)送給我們的應(yīng)用参咙,這樣我們的應(yīng)用就能識別請求來自哪個用戶了,這就是傳統(tǒng)的基于session認(rèn)證龄广。
但是這種基于session的認(rèn)證使應(yīng)用本身很難得到擴(kuò)展,隨著不同客戶端用戶的增加蕴侧,獨立的服務(wù)器已無法承載更多的用戶择同,而這時候基于session認(rèn)證應(yīng)用的問題就會暴露出來.
基于session認(rèn)證所顯露的問題
Session: 每個用戶經(jīng)過我們的應(yīng)用認(rèn)證之后,我們的應(yīng)用都要在服務(wù)端做一次記錄戈盈,以方便用戶下次請求的鑒別奠衔,通常而言session都是保存在內(nèi)存中,而隨著認(rèn)證用戶的增多塘娶,服務(wù)端的開銷會明顯增大。
擴(kuò)展性: 用戶認(rèn)證之后痊夭,服務(wù)端做認(rèn)證記錄刁岸,如果認(rèn)證的記錄被保存在內(nèi)存中的話,這意味著用戶下次請求還必須要請求在這臺服務(wù)器上,這樣才能拿到授權(quán)的資源她我,這樣在分布式的應(yīng)用上虹曙,相應(yīng)的限制了負(fù)載均衡器的能力。這也意味著限制了應(yīng)用的擴(kuò)展能力番舆。
CSRF: 因為是基于cookie來進(jìn)行用戶識別的, cookie如果被截獲酝碳,用戶就會很容易受到跨站請求偽造的攻擊。
基于token的鑒權(quán)機(jī)制
基于token的鑒權(quán)機(jī)制類似于http協(xié)議也是無狀態(tài)的恨狈,它不需要在服務(wù)端去保留用戶的認(rèn)證信息或者會話信息疏哗。這就意味著基于token認(rèn)證機(jī)制的應(yīng)用不需要去考慮用戶在哪一臺服務(wù)器登錄了,這就為應(yīng)用的擴(kuò)展提供了便利禾怠。
流程上是這樣的:
- 用戶使用用戶名密碼來請求服務(wù)器
- 服務(wù)器進(jìn)行驗證用戶的信息
- 服務(wù)器通過驗證發(fā)送給用戶一個token
- 客戶端存儲token返奉,并在每次請求時附送上這個token值
- 服務(wù)端驗證token值贝搁,并返回數(shù)據(jù)
這個token必須要在每次請求時傳遞給服務(wù)端,它應(yīng)該保存在請求頭里芽偏, 另外雷逆,服務(wù)端要支持CORS(跨來源資源共享)
策略,一般我們在服務(wù)端這么做就可以了Access-Control-Allow-Origin: *
污尉。
那么我們現(xiàn)在回到JWT的主題上膀哲。
JWT長什么樣?
JWT是由三段信息構(gòu)成的被碗,將這三段信息文本用.
鏈接一起就構(gòu)成了Jwt字符串等太。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT的構(gòu)成
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似于飛機(jī)上承載的物品),第三部分是簽證(signature).
header
jwt的頭部承載兩部分信息:
- 聲明類型蛮放,這里是jwt
- 聲明加密的算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后將頭部進(jìn)行base64加密(該加密是可以對稱解密的),構(gòu)成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload
載荷就是存放有效信息的地方缩抡。這個名字像是特指飛機(jī)上承載的貨品,這些有效信息包含三個部分
- 標(biāo)準(zhǔn)中注冊的聲明
- 公共的聲明
- 私有的聲明
標(biāo)準(zhǔn)中注冊的聲明 (建議但不強制使用) :
- iss: jwt簽發(fā)者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間包颁,這個過期時間必須要大于簽發(fā)時間
- nbf: 定義在什么時間之前瞻想,該jwt都是不可用的.
- iat: jwt的簽發(fā)時間
- jti: jwt的唯一身份標(biāo)識,主要用來作為一次性token,從而回避重放攻擊娩嚼。
公共的聲明 :
公共的聲明可以添加任何的信息蘑险,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.
私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明岳悟,一般不建議存放敏感信息佃迄,因為base64是對稱解密的,意味著該部分信息可以歸類為明文信息贵少。
定義一個payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后將其進(jìn)行base64加密呵俏,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
jwt的第三部分是一個簽證信息滔灶,這個簽證信息由三部分組成:
- header (base64后的)
- payload (base64后的)
- secret
這個部分需要base64加密后的header和base64加密后的payload使用.
連接組成的字符串普碎,然后通過header中聲明的加密方式進(jìn)行加鹽secret
組合加密,然后就構(gòu)成了jwt的第三部分录平。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
將這三部分用.
連接成一個完整的字符串,構(gòu)成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服務(wù)器端的麻车,jwt的簽發(fā)生成也是在服務(wù)器端的,secret就是用來進(jìn)行jwt的簽發(fā)和jwt的驗證斗这,所以动猬,它就是你服務(wù)端的私鑰,在任何場景都不應(yīng)該流露出去表箭。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發(fā)jwt了赁咙。
如何應(yīng)用
一般是在請求頭里加入Authorization
,并加上Bearer
標(biāo)注:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服務(wù)端會驗證token,如果驗證通過就會返回相應(yīng)的資源序目。整個流程就是這樣的:
總結(jié)
優(yōu)點
- 因為json的通用性臂痕,所以JWT是可以進(jìn)行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用猿涨。
- 因為有了payload部分握童,所以JWT可以在自身存儲一些其他業(yè)務(wù)邏輯所必要的非敏感信息。
- 便于傳輸叛赚,jwt的構(gòu)成非常簡單澡绩,字節(jié)占用很小,所以它是非常便于傳輸?shù)摹?/li>
- 它不需要在服務(wù)端保存會話信息, 所以它易于應(yīng)用的擴(kuò)展
安全相關(guān)
- 不應(yīng)該在jwt的payload部分存放敏感信息俺附,因為該部分是客戶端可解密的部分肥卡。
- 保護(hù)好secret私鑰,該私鑰非常重要事镣。
- 如果可以步鉴,請使用https協(xié)議