jwt(Json Web token) 是一種用于不同組織之間交換數(shù)據(jù)的格式。既然名稱(chēng)中包含了Json,顯而易見(jiàn),其數(shù)據(jù)結(jié)構(gòu)是以Json為基礎(chǔ)進(jìn)行搭建的。數(shù)據(jù)中包含了三個(gè)部分,header、payload以及token。其中token可以使用私鑰(HMAC算法),也可以基于公私鑰的形式,通過(guò)對(duì)header及payload進(jìn)行計(jì)算生成,從而達(dá)到校驗(yàn)header及payload數(shù)據(jù)合法性的作用。
使用場(chǎng)景:
- 跨域認(rèn)證。單點(diǎn)登錄系統(tǒng)是最常見(jiàn)的jwt使用場(chǎng)景。用戶(hù)在登錄后,系統(tǒng)返回jwt的信息。后續(xù)的所有請(qǐng)求都會(huì)帶上jwt的信息,從而認(rèn)證該用戶(hù)的信息。由于數(shù)據(jù)的請(qǐng)求可以通過(guò)header、cookies、參數(shù)等方式,使得跨域訪(fǎng)問(wèn)變得十分簡(jiǎn)單。
- 跨域數(shù)據(jù)分享。通過(guò)使用公私鑰的方法,客戶(hù)端可以使用公鑰對(duì)簽發(fā)的數(shù)據(jù)進(jìn)行校驗(yàn),從而保證數(shù)據(jù)不被篡改。
結(jié)構(gòu)
? jwt包含了三個(gè)部分
- header
- payload
- signature
從結(jié)構(gòu)上來(lái)看,三個(gè)部分都為base64-URL編碼后,使用.拼接后的數(shù)據(jù):
? eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
? eyJpc3MiOiJzdW1tZXJAdGVuY2VudCIsImFkbWluIjoiRmFsc2UiLCJleHAiOjE1MzkwNTMxODV9.
? Tc8VyoGFihW_CBXnCIX6eHzbLo3WEQH-Sy65ohSWfqM
header
? header包含了typ及alg兩個(gè)部分:
{
"alg": "HS256", // 計(jì)算的方法
"typ": "JWT" // 都為JTW
}
payload
payload則包含了本次聲明的數(shù)據(jù)。數(shù)據(jù)分為幾個(gè)類(lèi)型
- jwt協(xié)議中定義的聲明
-
iss簽發(fā)人 -
sub主題 -
aud受眾 -
exp過(guò)期時(shí)間 -
nbf生效時(shí)間 -
jtijwt id
-
- 通用的數(shù)據(jù)
- 私有的數(shù)據(jù): 用戶(hù)自己定義的數(shù)據(jù)
一個(gè)payload的樣例:
{
"sub": "userinfo",
"name": "testuser",
"admin": false
}
signature
signature是對(duì)header及payload的數(shù)據(jù)進(jìn)行簽名。簽名的算法可以使用HMAC RSA256,也可以使用RSA256.前者需要所有的使用者都擁有加密的secret,后者的使用者則需要RSA的public key用于數(shù)據(jù)的解密。
signature的算法通常如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
使用
從上面的定義來(lái)看,jwt本身是十分簡(jiǎn)單的。而且各種語(yǔ)言的JSON Parser都很全,所以相應(yīng)的庫(kù)也十分全面,基本常見(jiàn)的語(yǔ)言都是支持的。而且一種語(yǔ)言都有非常多的輪子,具體可以參考。
這里就簡(jiǎn)單的以python作為樣例,使用的是pyjwt,文檔請(qǐng)參考
HMAC
import jwt
key = 'secret'
encoded = jwt.encode({'sub': '123456', 'user': 'testuser'}, key, algorithm='HS256')
decoded = jwt.decode(encoded, key, algorithms='HS256')
RSA
RSA算法的用法和HMAC類(lèi)似
import jwt
encoded = jwt.encode({'sub': 123456, 'user': 'testuser'}, pri, algorithm='RS256')
decoded = jwt.decode(encoded, pub)
交互

jwt適用的場(chǎng)景是跨域的數(shù)據(jù)認(rèn)證。通常的使用流程如下:
- client需要訪(fǎng)問(wèn)服務(wù)資源,首先請(qǐng)求認(rèn)證服務(wù)器,通過(guò)相應(yīng)的參數(shù),獲取合法的
jwt token - client在后續(xù)所有的訪(fǎng)問(wèn)中,都在請(qǐng)求包帶上
jwt token - 資源的服務(wù)器校驗(yàn)該
jwttoken是否合法。由于jwt的特性,校驗(yàn)可以在服務(wù)器本地完成,而不需要去請(qǐng)求遠(yuǎn)端的認(rèn)證服務(wù)器。
請(qǐng)求時(shí),帶參數(shù)的方式有多種:
-
cookies
如果認(rèn)證服務(wù)器與資源服務(wù)器是同域的,則可以把jwt token放在cookies進(jìn)行攜帶。
-
header
對(duì)于跨域的情況,則可以在請(qǐng)求的header中添加
Authorization: bearer <token>字段的方式來(lái)傳遞。由于是在header中添加數(shù)據(jù),所以沒(méi)有跨域的cookies無(wú)法攜帶的問(wèn)題。 -
params
還有就是在請(qǐng)求的參數(shù)中增加token字段。比如
Get方法中的url&token=xxx的形式,或者是Post中的請(qǐng)求參數(shù)
tips
使用claim校驗(yàn)
jwt官方定義了幾種常用的claim,用于對(duì)數(shù)據(jù)合法性進(jìn)行校驗(yàn).常用的有:
-
exp: token的過(guò)期時(shí)間。
服務(wù)端或者客戶(hù)端進(jìn)行decode時(shí),可以自動(dòng)捕獲超時(shí)錯(cuò)誤。
jwt.encode({'exp': datetime.utcnow()}, 'secret') try: jwt.decode('JWT_STRING', 'secret', algorithms=['HS256']) except jwt.ExpiredSignatureError: # Signature has expired nbf: token的啟用時(shí)間
iss: token的簽發(fā)者
aud: token的接受者
stateful & unstateful
stateful和unstateful是由jwt中數(shù)據(jù)存儲(chǔ)的內(nèi)容決定的. 如果jwt中的數(shù)據(jù)包含了所需要的全部數(shù)據(jù), 每個(gè)client在使用數(shù)據(jù)時(shí), 不需要再到某個(gè)服務(wù)中進(jìn)行合法性校驗(yàn), 則這個(gè)jwt是unstateful的, 反之, 則是stateful的.
舉個(gè)例子:
{
"uid": 123456,
"exp": 1539148957,
"group": "admin",
"company": "1"
}
上面這個(gè)jwt包含了uid, group, company字段. 這些數(shù)據(jù)已經(jīng)足夠處理后續(xù)的所有請(qǐng)求了. 因此, client在執(zhí)行的時(shí)候, 不需要再請(qǐng)求一次服務(wù)器, 獲取剩余的數(shù)據(jù). 這種jwt, 可以稱(chēng)之為 unstateful的.
再看下面這個(gè):
{
"uid": "sess-1234-412-x12",
"exp": 1539148957
}
這個(gè)jwt中僅僅包含了一個(gè)uid信息, 所有的數(shù)據(jù)都沒(méi)有. 在使用時(shí), 還需要再請(qǐng)求一個(gè)服務(wù)獲取相應(yīng)的數(shù)據(jù), 這種jwt就是stateful的.
當(dāng)然, stateful的jwt, 也可以包含所有的數(shù)據(jù).
{
"uid": 123456,
"exp": 1539148957,
"group": "admin",
"company": "1",
"tid": "123-xx-xx"
}
在使用該jwt之前, 需要首先校驗(yàn)該tid是否還是合法. 一般會(huì)在一個(gè)服務(wù)器中維護(hù)一個(gè)tid的黑名單. 如果該tid屬于黑名單中, 則強(qiáng)制廢除該jwt.
安全問(wèn)題
雖然token看起來(lái)是一段加密后的數(shù)據(jù), 而且還用了加密算法, 但實(shí)際上并不是加密的. 回顧下token的樣子,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOjEyMzQ1NiwidXNlciI6InRlc3R1c2VyIn0.
NAHaQSXelvub7_y3XSXpofBQDuzBrGGxeJ4SAQPkYRE
token分成了三段:
- header
- payload
- token
其中, header\payload都是僅僅用base64-URL encode后的結(jié)果. 也就是說(shuō),誰(shuí)都可以做decode獲取數(shù)據(jù). 所以, jwt中一定不能包含涉密的數(shù)據(jù),尤其是用戶(hù)密碼等敏感信息
性能問(wèn)題
這里的性能問(wèn)題, 指的是該結(jié)構(gòu)帶來(lái)的請(qǐng)求數(shù)據(jù)變多的問(wèn)題. 在jwt的使用場(chǎng)景中, 所有的請(qǐng)求都需要完整帶上jwt的數(shù)據(jù). 由于payload這部分的數(shù)據(jù)僅僅是base64后的數(shù)據(jù), 并沒(méi)有做任何處理. 所以, 如果在payload中增加過(guò)多的數(shù)據(jù), 就會(huì)導(dǎo)致jwt的結(jié)構(gòu)變大, 從而導(dǎo)致請(qǐng)求的效率降低.
替代session
jwt的競(jìng)爭(zhēng)對(duì)手并不是傳統(tǒng)的session, 而是其他的跨域認(rèn)證方案, 例如SAML以及SWT. 但是, 也有很多人使用jwt作為session的替代品進(jìn)行使用. 選擇jwt的原因大多數(shù)如下:
-
平行擴(kuò)容
使用session的服務(wù)在用了jwt后, 完全可以在本地對(duì)jwt發(fā)過(guò)來(lái)的數(shù)據(jù)進(jìn)行校驗(yàn), 從而不需要傳統(tǒng)的session記錄. 因此, 當(dāng)服務(wù)快速平行擴(kuò)容時(shí), 也不會(huì)因?yàn)閟ession記錄找不到而引起問(wèn)題.
-
免去session服務(wù)器
同樣的, 因?yàn)閖wt已經(jīng)記錄了用戶(hù)相關(guān)的信息, 也就不需要再去session獲取一次用戶(hù)的數(shù)據(jù). 從而避免session服務(wù)器成為卡點(diǎn).
當(dāng)然, 這兩點(diǎn)優(yōu)勢(shì)也飽受質(zhì)疑.
首先, 現(xiàn)在session大多都直接存放在redis, memcache等緩存中, 已經(jīng)沒(méi)有什么人使用本地的文件存儲(chǔ)了. 此外, 也可以通過(guò)負(fù)載均衡來(lái)保證訪(fǎng)問(wèn)都單用戶(hù)請(qǐng)求都落在單一服務(wù)器上.
而對(duì)于免去session服務(wù)器, 則要看如何定義jwt中的數(shù)據(jù). 前面說(shuō)過(guò), jwt可以是unstateful或者是stateful的. 對(duì)于unstateful的jwt, 當(dāng)?shù)卿浄?wù)器簽發(fā)了之后, 該jwt只要在exp之前都長(zhǎng)期有效. 所以服務(wù)端是無(wú)法知道當(dāng)前有多少用戶(hù)在線(xiàn). 這對(duì)于某些應(yīng)用是不合適的. 而且, 廢除jwt也成為了件難事. 如果需要廢除已經(jīng)簽發(fā), 但仍在有效期的jwt, 就需要添加一個(gè)黑名單, 那就變成了stateful的jwt, 而且也需要一個(gè)集中化的服務(wù).
結(jié)論
jwt在2015年就已經(jīng)成為RFC標(biāo)準(zhǔn), 其token生成的設(shè)計(jì), 很好的解決了不同組織之間相互數(shù)據(jù)信任的問(wèn)題, 因而在單點(diǎn)認(rèn)證方面得到了大規(guī)模的應(yīng)用.
在單體服務(wù)中, jwt也被很多人用來(lái)作為session的替代品進(jìn)行使用, 也引來(lái)了很多的討論, 甚至是互噴. 整體上來(lái)說(shuō), 經(jīng)過(guò)設(shè)計(jì), 對(duì)于某些場(chǎng)景下, 由于簽發(fā)的數(shù)據(jù)天然就帶了可信任的token, 所以不需要再進(jìn)行遠(yuǎn)端校驗(yàn), 確實(shí)是能提高系統(tǒng)整體的吞吐率.