1. 摘要
本文講解JWT(JSON Web Token )的定義,機(jī)制猾浦,格式和在跨域多網(wǎng)站單點(diǎn)登錄中的應(yīng)用为迈。
2.內(nèi)容
2.1 什么是JWT ?
Json web token(JWT)是為了網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開(kāi)發(fā)標(biāo)準(zhǔn)(RFC 7519),該token被設(shè)計(jì)為緊湊且安全的,特別適用于分布式站點(diǎn)的單點(diǎn)登陸(SSO)場(chǎng)景铛纬。JWT的聲明一般被用來(lái)在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源唬滑,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息告唆,該token也可直接被用于認(rèn)證,也可被加密间雀。
通俗來(lái)講悔详,JWT是一個(gè)含簽名并攜帶用戶相關(guān)信息的加密串,頁(yè)面請(qǐng)求校驗(yàn)登錄接口時(shí)惹挟,請(qǐng)求頭中攜帶JWT串到后端服務(wù)茄螃,后端通過(guò)簽名加密串匹配校驗(yàn),保證信息未被篡改连锯。校驗(yàn)通過(guò)則認(rèn)為是可靠的請(qǐng)求归苍,將正常返回?cái)?shù)據(jù)。
什么情況下使用JWT比較適合运怖?
- 授權(quán):
這是最常見(jiàn)的使用場(chǎng)景拼弃,解決單點(diǎn)登錄問(wèn)題。因?yàn)镴WT使用起來(lái)輕便摇展,開(kāi)銷小吻氧,服務(wù)端不用記錄用戶狀態(tài)信息(無(wú)狀態(tài)),所以使用比較廣泛咏连; - 信息交換:
JWT是在各個(gè)服務(wù)之間安全傳輸信息的好方法盯孙。因?yàn)镴WT可以簽名,例如祟滴,使用公鑰/私鑰對(duì)兒 - 可以確定請(qǐng)求方是合法的振惰。此外,由于使用標(biāo)頭和有效負(fù)載計(jì)算簽名垄懂,還可以驗(yàn)證內(nèi)容是否未被篡改骑晶。
2.2 JWT 的數(shù)據(jù)結(jié)構(gòu)
2.2.1 傳統(tǒng) session 認(rèn)證及其弊病
互聯(lián)網(wǎng)服務(wù)離不開(kāi)用戶認(rèn)證痛垛。一般流程是下面這樣。
1桶蛔、用戶向服務(wù)器發(fā)送用戶名和密碼匙头。
2、服務(wù)器驗(yàn)證通過(guò)后仔雷,在當(dāng)前對(duì)話(session)里面保存相關(guān)數(shù)據(jù)乾胶,比如用戶角色、登錄時(shí)間等等朽寞。
3、服務(wù)器向用戶返回一個(gè) session_id斩郎,寫(xiě)入用戶的 Cookie脑融。
4、用戶隨后的每一次請(qǐng)求缩宜,都會(huì)通過(guò) Cookie肘迎,將 session_id 傳回服務(wù)器。
5锻煌、服務(wù)器收到 session_id妓布,找到前期保存的數(shù)據(jù),由此得知用戶的身份宋梧。
這種模式的問(wèn)題在于匣沼,擴(kuò)展性(scaling)不好。單機(jī)當(dāng)然沒(méi)有問(wèn)題捂龄,如果是服務(wù)器集群释涛,或者是跨域的服務(wù)導(dǎo)向架構(gòu),就要求 session 數(shù)據(jù)共享倦沧,每臺(tái)服務(wù)器都能夠讀取 session唇撬。
舉例來(lái)說(shuō),A 網(wǎng)站和 B 網(wǎng)站是同一家公司的關(guān)聯(lián)服務(wù)≌谷冢現(xiàn)在要求窖认,用戶只要在其中一個(gè)網(wǎng)站登錄,再訪問(wèn)另一個(gè)網(wǎng)站就會(huì)自動(dòng)登錄告希,請(qǐng)問(wèn)怎么實(shí)現(xiàn)扑浸?
一種解決方案是 session 數(shù)據(jù)持久化,寫(xiě)入數(shù)據(jù)庫(kù)或別的持久層暂雹。各種服務(wù)收到請(qǐng)求后首装,都向持久層請(qǐng)求數(shù)據(jù)。這種方案的優(yōu)點(diǎn)是架構(gòu)清晰杭跪,缺點(diǎn)是工程量比較大仙逻。另外驰吓,持久層萬(wàn)一掛了,就會(huì)單點(diǎn)失敗系奉。
另一種方案是服務(wù)器索性不保存 session 數(shù)據(jù)了檬贰,所有數(shù)據(jù)都保存在客戶端,每次請(qǐng)求都發(fā)回服務(wù)器缺亮。JWT 就是這種方案的一個(gè)代表翁涤。
2.2.2 JWT 的原理
JWT 的原理是,服務(wù)器認(rèn)證以后萌踱,生成一個(gè) JSON 對(duì)象葵礼,發(fā)回給用戶,就像下面這樣.
{
"姓名": "張三",
"角色": "管理員",
"到期時(shí)間": "2018年7月1日0點(diǎn)0分"
}
以后并鸵,用戶與服務(wù)端通信的時(shí)候鸳粉,都要發(fā)回這個(gè) JSON 對(duì)象。服務(wù)器完全只靠這個(gè)對(duì)象認(rèn)定用戶身份园担。為了防止用戶篡改數(shù)據(jù)届谈,服務(wù)器在生成這個(gè)對(duì)象的時(shí)候,會(huì)加上簽名(詳見(jiàn)后文)弯汰。
服務(wù)器就不保存任何 session 數(shù)據(jù)了艰山,也就是說(shuō),服務(wù)器變成無(wú)狀態(tài)了咏闪,從而比較容易實(shí)現(xiàn)擴(kuò)展曙搬。
2.2.3 JWT 的數(shù)據(jù)結(jié)構(gòu)
實(shí)際的 JWT 大概就像下面這樣。
它是一個(gè)很長(zhǎng)的字符串鸽嫂,中間用點(diǎn)(.
)分隔成三個(gè)部分织鲸。注意,JWT 內(nèi)部是沒(méi)有換行的溪胶,這里只是為了便于展示搂擦,將它寫(xiě)成了幾行。
JWT 的三個(gè)部分依次如下哗脖。
- Header(頭部)
- Payload(負(fù)載)
- Signature(簽名)
寫(xiě)成一行瀑踢,就是下面的樣子。
Header.Payload.Signature
下面依次介紹這三個(gè)部分才避。
2.2.3.1 Header
Header 部分是一個(gè) JSON 對(duì)象橱夭,描述 JWT 的元數(shù)據(jù),通常是下面的樣子桑逝。
{ "alg": "HS256", "typ": "JWT" }
上面代碼中棘劣,alg
屬性表示簽名的算法(algorithm),默認(rèn)是 HMAC SHA256(寫(xiě)成 HS256)楞遏;typ
屬性表示這個(gè)令牌(token)的類型(type)茬暇,JWT 令牌統(tǒng)一寫(xiě)為JWT
首昔。
最后,將上面的 JSON 對(duì)象使用 Base64URL 算法(詳見(jiàn)后文)轉(zhuǎn)成字符串糙俗。
2.2.3.2 Payload
Payload 部分也是一個(gè) JSON 對(duì)象勒奇,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)。JWT 規(guī)定了7個(gè)官方字段巧骚,供選用赊颠。
- iss (issuer):簽發(fā)人
- exp (expiration time):過(guò)期時(shí)間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時(shí)間
- iat (Issued At):簽發(fā)時(shí)間
- jti (JWT ID):編號(hào)
除了官方字段,你還可以在這個(gè)部分定義私有字段劈彪,下面就是一個(gè)例子竣蹦。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
注意,JWT 默認(rèn)是不加密的沧奴,任何人都可以讀到草添,所以不要把秘密信息放在這個(gè)部分。
這個(gè) JSON 對(duì)象也要使用 Base64URL 算法轉(zhuǎn)成字符串扼仲。
2.2.3.3 Signature
Signature 部分是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改抄淑。
首先屠凶,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道肆资,不能泄露給用戶矗愧。然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256)郑原,按照下面的公式產(chǎn)生簽名唉韭。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出簽名以后,把 Header犯犁、Payload属愤、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"(.
)分隔酸役,就可以返回給用戶住诸。
2.2.3.4 Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL涣澡。這個(gè)算法跟 Base64 算法基本類似贱呐,但有一些小的不同。
JWT 作為一個(gè)令牌(token)入桂,有些場(chǎng)合可能會(huì)放到 URL(比如 api.example.com/?token=xxx)奄薇。Base64 有三個(gè)字符+
、/
和=
抗愁,在 URL 里面有特殊含義馁蒂,所以要被替換掉:=
被省略呵晚、+
替換成-
,/
替換成_
远搪。這就是 Base64URL 算法劣纲。
2.2.3.4
客戶端收到服務(wù)器返回的 JWT,可以儲(chǔ)存在 Cookie 里面谁鳍,也可以儲(chǔ)存在 localStorage癞季。
此后,客戶端每次與服務(wù)器通信倘潜,都要帶上這個(gè) JWT绷柒。你可以把它放在 Cookie 里面自動(dòng)發(fā)送,但是這樣不能跨域涮因,所以更好的做法是放在 HTTP 請(qǐng)求的頭信息Authorization
字段里面废睦。
Authorization: Bearer <token>
另一種做法是,跨域的時(shí)候养泡,JWT 就放在 POST 請(qǐng)求的數(shù)據(jù)體里面嗜湃。
2.3 JWT 認(rèn)證流程
常規(guī)的 JWT 認(rèn)證流程如下如:
相比于 session 認(rèn)證,JWT 省去了服務(wù)器存儲(chǔ)用戶信息的過(guò)程澜掩。
JWT 常見(jiàn)校驗(yàn)流程:
其中設(shè)置 payload 校驗(yàn)規(guī)則一般是檢查 nbf购披、exp 以及用戶自定義的一些字段。
2.4 使用JWT實(shí)現(xiàn)單點(diǎn)登錄(完全跨域方案)
基于 cookie 的單點(diǎn)登錄模式有一個(gè)弊病在于肩榕,其對(duì)應(yīng)的多個(gè)站點(diǎn)的頂級(jí)域名必須相同刚陡。
主要有以下三步:
項(xiàng)目一開(kāi)始我先封裝了一個(gè)JWTHelper工具包(GitHub下載),主要提供了生成JWT株汉、解析JWT以及校驗(yàn)JWT的方法筐乳,其他還有一些加密相關(guān)操作。
接下來(lái)乔妈,我在客戶端項(xiàng)目中依賴JWTHelper工具包蝙云,并添加Interceptor攔截器,攔截需要校驗(yàn)登錄的接口路召。攔截器中校驗(yàn)JWT有效性贮懈,并在response中重新設(shè)置JWT的新值;
最后在JWT服務(wù)端优训,依賴JWT工具包朵你,在登錄方法中,需要在登錄校驗(yàn)成功后調(diào)用生成JWT方法揣非,生成一個(gè)JWT令牌并且設(shè)置到response的header中抡医。
參考 使用JWT實(shí)現(xiàn)單點(diǎn)登錄(完全跨域方案)https://blog.csdn.net/weixin_42873937/article/details/82460997
2.5 access_token和refresh_token
通常在使用JWT的時(shí)候會(huì)設(shè)置access_token和refresh_token兩個(gè)token,access_token有效期較短,refresh_token有效期較長(zhǎng)忌傻,當(dāng)access_token過(guò)期之后大脉,如果refresh_token沒(méi)有過(guò)期則可以換取新的access_token。我的疑問(wèn)在于為什么不直接給access_token設(shè)置一個(gè)較長(zhǎng)的有效期水孩。如果是為了安全镰矿,能拿到access_token,拿到refresh_token就是輕而易舉的事俘种;還有的說(shuō)法是refresh_token中可以存放比access_token更多秤标、驗(yàn)證起來(lái)更復(fù)雜的信息。
Token作為用戶獲取受保護(hù)資源的憑證宙刘,必須設(shè)置一個(gè)過(guò)期時(shí)間苍姜,否則一次登錄便可永久使用,認(rèn)證功能就失去了意義悬包。但是矛盾在于:過(guò)期時(shí)間設(shè)置得太長(zhǎng)衙猪,用戶數(shù)據(jù)的安全性將大打折扣;過(guò)期時(shí)間設(shè)置得太短布近,用戶就必須每隔一段時(shí)間重新登錄垫释,以獲取新的憑證,這會(huì)極大挫傷用戶的積極性撑瞧。針對(duì)這一問(wèn)題棵譬,我們可以利用Access / Refresh Token這一概念來(lái)平衡Token安全性和用戶體驗(yàn)。
Access / Refresh Token是什么季蚂?
上圖表示Access/Refresh Token在客戶端、認(rèn)證服務(wù)器琅束、資源服務(wù)器三者之間的傳遞關(guān)系扭屁,簡(jiǎn)單來(lái)說(shuō):
- Access Token即“訪問(wèn)令牌”,是客戶端向資源服務(wù)器換取資源的憑證涩禀;
- Refresh Token即“刷新令牌”料滥,是客戶端向認(rèn)證服務(wù)器換取Access Token的憑證。
Access / Refresh Token如何使用艾船?
上圖表示客戶端請(qǐng)求資源的過(guò)程中葵腹,Access Token 和 Refresh Token 是如何配合使用的:
1. 用戶提供身份信息(一般是[用戶名/密碼],利用客戶端向認(rèn)證服務(wù)器換取 Refresh Token和Access Token)屿岂;
2. 客戶端攜帶Access Token訪問(wèn)資源服務(wù)器践宴,資源服務(wù)器識(shí)別Access Token并返回資源;
3. 當(dāng)Access Token過(guò)期或失效爷怀,客戶端再一次訪問(wèn)資源服務(wù)器阻肩,資源服務(wù)器返回“無(wú)效token”報(bào)錯(cuò);
4. 客戶端通過(guò)Refresh Token向認(rèn)證服務(wù)器換取Access Token运授,認(rèn)證服務(wù)器返回新的Access Token烤惊。
3. 參考
(1)10分鐘了解JSON Web令牌(JWT)
https://baijiahao.baidu.com/s?id=1608021814182894637&wfr=spider&for=pc
(2)JWT的使用流程
https://blog.csdn.net/shmely/article/details/85915044
(3)JWT全面解讀乔煞、使用步驟
https://blog.csdn.net/achenyuan/article/details/80829401
(4)JSON Web Token 入門教程 -阮一峰
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
【說(shuō)明】jwt結(jié)構(gòu)講得比較清晰。
(5)JWT(JSON Web Tokens)
https://www.cnblogs.com/zaixiuxing/p/6005968.html
(6)使用JWT實(shí)現(xiàn)單點(diǎn)登錄(完全跨域方案)
https://blog.csdn.net/weixin_42873937/article/details/82460997
(7)基于 JWT 的單點(diǎn)登錄設(shè)計(jì)
http://www.reibang.com/p/6c4e1804653f
【說(shuō)明】有邏輯圖柒室,介紹得清晰渡贾。
(8)JWT全面解讀、使用步驟
https://blog.csdn.net/achenyuan/article/details/80829401
【說(shuō)明】JAVA實(shí)現(xiàn)代碼
(9)看圖理解JWT如何用于單點(diǎn)登錄
https://www.shuzhiduo.com/A/kPzORnodxn/