why JWT
現(xiàn)在,前后端分離和 RESTful API 越來(lái)越火熱,當(dāng)后臺(tái)漸漸開始只負(fù)責(zé)為客戶端提供 API 接口之后,身份校驗(yàn)和接口安全成了難題。在傳統(tǒng)的開發(fā)模式下,使用 cookie-session 可以保證接口安全,在沒(méi)有登錄的情況下訪問(wèn)關(guān)鍵數(shù)據(jù)會(huì)跳轉(zhuǎn)到登錄界面或者請(qǐng)求失敗。而使用 REStful API 之后,cookie-session 存在以下 3 個(gè)問(wèn)題:
- 客戶端除了瀏覽器,可能還包括手機(jī)端 APP,對(duì)于手機(jī)端而言,管理 cookie 是一件麻煩的事情。
- RESTful 風(fēng)格的 API 不建議使用 cookie。
- cookie 本身有一個(gè)缺陷,不能跨域。
正是存在上面的幾個(gè)缺陷,現(xiàn)在 API 開始使用 JWT 代替 cookie-session 來(lái)做身份驗(yàn)證。
what JWT
JWT 全稱 JSON Web Token。本質(zhì)上 JWT 是一串 token 字符串。客戶端登錄之后,服務(wù)端返回一串 token 給客戶端,之后每次客戶端請(qǐng)求 API 接口都需要攜帶該 token 進(jìn)行身份校驗(yàn)。JWT 由三個(gè)部分組成:頭部(header)、載荷(payload)、簽名(signature)。這三個(gè)部分使用 .
連接在一起就是一個(gè)完整的 JWT。所以,一個(gè)完整的 JWT 應(yīng)該類似下面這種形式:
xxxxxx.yyyyy.zzzzz
header
header 是一個(gè) json 數(shù)據(jù),用于描述 JWT 的基本信息。一般要由兩個(gè)部分組成:
- alg
- typ
alg 代表的是加密所使用的算法(后面會(huì)提到加密數(shù)據(jù)),typ 表示該 token 是什么類型的。這里 typ 自然是 JWT。
{
'alg':'HS256',
'typ':'JWT'
}
一個(gè)完整的 header 信息。最后使用 Base64 對(duì) header 進(jìn)行編碼,得到 JWT 的第一部分。
payload
payload 是 JWT 存儲(chǔ)信息的部分。payload 也是一個(gè) json 數(shù)據(jù),每一個(gè) json 的 key-value 稱為一個(gè)聲明。
payload 有兩種類型的聲明:標(biāo)準(zhǔn)聲明和自定義聲明。
標(biāo)準(zhǔn)聲明一共有 6 個(gè),其名稱和對(duì)應(yīng)含義如下:
-
iss
: JWT 的簽發(fā)者。 -
iat
: JWT 的簽發(fā)時(shí)間,是一個(gè) unix 時(shí)間戳。 -
exp
: JWT 的過(guò)期時(shí)間,是一個(gè) unxi 時(shí)間戳。 -
aud
: 接受 JWT 的一方。 -
sub
: JWT 所面向的用戶。 -
jti
: 唯一標(biāo)識(shí)一個(gè) JWT。
自定義聲明為用戶自己定義的 key-value,可以用來(lái)存儲(chǔ)一些簡(jiǎn)單的基本信息。考慮到性能,不應(yīng)該在 payload 中定義太多自定義聲明。
定義一個(gè) payload :
{
"iss": "jaychen",
"iat": 1441593502,
"exp": 1441594722,
"aud": "jaychen.cc",
"sub": "chenjiayaooo@gmail.com",
"jti:" "xxxxxxxx",
"user_id": "1",
"username": "jaychen"
}
上面這個(gè) payload 中,id
和 username
為自定義聲明。
有了 payload 只有,將該 payload 進(jìn)行 Base64 加密,得到一串字符串之后,用 .
把 header 和 payload 連接起來(lái)。
signature
將 header 和 payload 兩個(gè)部分連接起來(lái)之后,得到的字符串類似下面
xxxxx.yyyyy
接著,使用 header.alg
定義的加密算法對(duì) hader.payload
的字符串進(jìn)行加密,并且加密的時(shí)候應(yīng)該有一個(gè)密鑰。加密之后,得到一串加密字符串,最后把這串加密字符串也是用 .
拼接在 header.payload
后面,形成完整的 JWT。
這里簽名的目的是為了保證 payload 數(shù)據(jù)的完整性。如果 JWT 在傳輸過(guò)程中被第三方劫持,中間人對(duì) header.payload
進(jìn)行修改,并且使用自己的密鑰重新簽名。服務(wù)端收到中間人修改過(guò)的 JWT,使用自己的密鑰對(duì) header.payload
進(jìn)行再次加密,由于中間人和服務(wù)端使用的是不同的密鑰簽名,所以服務(wù)端再次加密的結(jié)果肯定和中間人加密的結(jié)果不一致,由此可以斷定該 JWT 被惡意篡改。
基于 JWT 的身份驗(yàn)證
現(xiàn)在已經(jīng)明白了 JWT 的生成過(guò)程,現(xiàn)在來(lái)梳理下 JWT 的使用流程。
- 首次登陸系統(tǒng),向服務(wù)端發(fā)送 username&&password 進(jìn)行登錄。
- 服務(wù)端驗(yàn)證 username&&password,驗(yàn)證合法為客戶端生成一串 JWT,這里在 payload 中可以自定義聲明 user_id,username 等字段用來(lái)保存信息。
- 客戶端收到服務(wù)端的 JWT 字符串,自行保存。后續(xù)需要請(qǐng)求 API 都要攜帶該 JWT 到服務(wù)端進(jìn)行身份校驗(yàn)。
- 服務(wù)端收到客戶端的 API 請(qǐng)求,先獲取 JWT 信息,通過(guò)簽名判斷 JWT 的合法性,如果合法,返回?cái)?shù)據(jù)。
JWT 的優(yōu)點(diǎn)和注意事項(xiàng)
注意事項(xiàng)
上面生成 JWT 的過(guò)程中使用了 Base64 的加密算法對(duì) payload 進(jìn)行加密,Base64 是一種可逆的加密算法,這意味著其他人可以輕易的從加密結(jié)果中得到加密之前的信息,所以這注定了 payload 中不能保存密碼之類的敏感信息。
優(yōu)點(diǎn)
回顧上面生成 JWT 的步驟,payload 中我們保存了 user_id
和 username
這樣的信息。在傳統(tǒng)的 cookie-session 中,這些數(shù)據(jù)是服務(wù)端在 session 中維護(hù)的。JWT 把之前需要在服務(wù)端維護(hù)的 session 數(shù)據(jù)轉(zhuǎn)移到客戶端,使得服務(wù)端的壓力小了很多。
JWT 本質(zhì)只是一串字符串,所以可以無(wú)限制的使用各種姿勢(shì)傳遞給服務(wù)端:當(dāng)做 get 參數(shù)拼接在 URL 中、添加到 header 頭部中、當(dāng)做 post 參數(shù)傳遞。。。
如果客戶端是手機(jī) APP 等非瀏覽器客戶端,那么使用 JWT 就可以免去對(duì) cookie 的管理。
本文首發(fā)于:https://jaychen.cc
作者:jaychen