理解JWT結(jié)構(gòu)化令牌,能讓自己對(duì)認(rèn)證、授權(quán)流程有更深入的理解。
JWT結(jié)構(gòu)化令牌
關(guān)于什么是 JWT,官方定義是這樣描述的:
JSON Web Token(JWT)是一個(gè)開(kāi)放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為 JSON 對(duì)象在各方之間安全地傳輸信息。
這個(gè)定義是不是很費(fèi)解?我們簡(jiǎn)單理解下,JWT 就是用一種結(jié)構(gòu)化封裝的方式來(lái)生成 token 的技術(shù)。結(jié)構(gòu)化后的 token 可以被賦予非常豐富的含義,這也是它與原先毫無(wú)意義的、隨機(jī)的字符串形式 token 的最大區(qū)別。
結(jié)構(gòu)化之后,令牌本身就可以被“塞進(jìn)”一些有用的信息.
JWT 這種結(jié)構(gòu)化體可以分為 HEADER(頭部)、PAYLOAD(數(shù)據(jù)體)和 SIGNATURE(簽名)三部分。經(jīng)過(guò)簽名之后的 JWT 的整體結(jié)構(gòu),是被句點(diǎn)符號(hào)分割的三段內(nèi)容,結(jié)構(gòu)為 header.payload.signature 。比如下面這個(gè)示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
注意:JWT 內(nèi)部沒(méi)有換行,這里只是為了展示方便,才將其用三行來(lái)表示。
你可能會(huì)說(shuō),這個(gè) JWT 令牌看起來(lái)也是毫無(wú)意義的、隨機(jī)的字符串啊。確實(shí),你直接去看這個(gè)字符串是沒(méi)啥意義,但如果你把它拷貝到https://jwt.io/ 網(wǎng)站的在線校驗(yàn)工具中,就可以看到解碼之后的數(shù)據(jù):

HEADER 表示裝載令牌類型和算法等信息,是 JWT 的頭部。其中,typ 表示第二部分 PAYLOAD 是 JWT 類型,alg 表示使用 HS256 對(duì)稱簽名的算法。
PAYLOAD 表示是 JWT 的數(shù)據(jù)體,代表了一組數(shù)據(jù)。其中,sub(令牌的主體,一般設(shè)為資源擁有者的唯一標(biāo)識(shí))、exp(令牌的過(guò)期時(shí)間戳)、iat(令牌頒發(fā)的時(shí)間戳)是 JWT 規(guī)范性的聲明,代表的是常規(guī)性操作。更多的通用聲明,你可以參考RFC 7519 開(kāi)放標(biāo)準(zhǔn)。不過(guò),在一個(gè) JWT 內(nèi)可以包含一切合法的 JSON 格式的數(shù)據(jù),也就是說(shuō),PAYLOAD 表示的一組數(shù)據(jù)允許我們自定義聲明。
SIGNATURE 表示對(duì) JWT 信息的簽名。那么,它有什么作用呢?我們可能認(rèn)為,有了 HEADER 和 PAYLOAD 兩部分內(nèi)容后,就可以讓令牌攜帶信息了,似乎就可以在網(wǎng)絡(luò)中傳輸了,但是在網(wǎng)絡(luò)中傳輸這樣的信息體是不安全的,因?yàn)槟阍凇奥惚肌卑 K?,我們還需要對(duì)其進(jìn)行加密簽名處理,而 SIGNATURE 就是對(duì)信息的簽名結(jié)果,當(dāng)受保護(hù)資源接收到第三方軟件的簽名后需要驗(yàn)證令牌的簽名是否合法。
0Auth2.0 與 JWT結(jié)構(gòu)化令牌
OAuth2.0 本質(zhì)是一個(gè)授權(quán)協(xié)議。
OAuth2.0 的核心是授權(quán)許可,更進(jìn)一步說(shuō)就是令牌機(jī)制。
授權(quán)服務(wù)的核心就是頒發(fā)訪問(wèn)令牌,而 OAuth 2.0 規(guī)范并沒(méi)有約束訪問(wèn)令牌內(nèi)容的生成規(guī)則,只要符合唯一性、不連續(xù)性、不可猜性就夠了。這就意味著,我們可以靈活選擇令牌的形式,既可以是沒(méi)有內(nèi)部結(jié)構(gòu)且不包含任何信息含義的隨機(jī)字符串,也可以是具有內(nèi)部結(jié)構(gòu)且包含有信息含義的字符串。
JWT令牌就是使用最多的結(jié)構(gòu)化令牌。
JWT令牌的使用
JWT令牌傳輸過(guò)程中需要進(jìn)行 Base64 編碼以防止亂碼,同時(shí)還需要進(jìn)行簽名及加密處理來(lái)防止數(shù)據(jù)信息泄露。
如果是我們自己處理這些編碼、加密等工作的話,就會(huì)增加額外的編碼負(fù)擔(dān)。好在,我們可以借助一些開(kāi)源的工具來(lái)幫助我們處理這些工作。比如,我在下面的 Demo 中,給出了開(kāi)源 JJWT(Java JWT)的使用方法。
JJWT 是目前 Java 開(kāi)源的、比較方便的 JWT 工具,封裝了 Base64URL 編碼和對(duì)稱 HMAC、非對(duì)稱 RSA 的一系列簽名算法。使用 JJWT,我們只關(guān)注上層的業(yè)務(wù)邏輯實(shí)現(xiàn),而無(wú)需關(guān)注編解碼和簽名算法的具體實(shí)現(xiàn),這類開(kāi)源工具可以做到“開(kāi)箱即用”。
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
String sharedTokenSecret="HelloWorld";//密鑰
Key key = new SecretKeySpec(sharedTokenSecret.getBytes(),
SignatureAlgorithm.HS256.getJcaName());
//生成JWT令牌
String jwts=
Jwts.builder().setHeaderParams(headerMap).setClaims(payloadMap).signWith(key,SignatureAlgorithm.HS256).compact()
//解析JWT令牌
Jws<Claims> claimsJws =Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwts);
JwsHeader header = claimsJws.getHeader();
Claims body = claimsJws.getBody();
JWT令牌缺陷與解決方案
JWT 格式令牌的最大問(wèn)題在于 “覆水難收”,也就是說(shuō),沒(méi)辦法在使用過(guò)程中修改令牌狀態(tài)。用戶或者第三方使用過(guò)程中,無(wú)法主動(dòng)暫停授權(quán)。
為了解決這個(gè)問(wèn)題,我們可以把 JWT 令牌存儲(chǔ)到遠(yuǎn)程的分布式內(nèi)存數(shù)據(jù)庫(kù)中嗎?顯然不能,因?yàn)檫@會(huì)違背 JWT 的初衷(將信息通過(guò)結(jié)構(gòu)化的方式存入令牌本身)。因此,我們通常會(huì)有兩種做法:
- 一是,將每次生成 JWT 令牌時(shí)的秘鑰粒度縮小到用戶級(jí)別,也就是一個(gè)用戶一個(gè)秘鑰。這樣,當(dāng)用戶取消授權(quán)或者修改密碼后,就可以讓這個(gè)密鑰一起修改。一般情況下,這種方案需要配套一個(gè)單獨(dú)的密鑰管理服務(wù)。
- 二是,在不提供用戶主動(dòng)取消授權(quán)的環(huán)境里面,如果只考慮到修改密碼的情況,那么我們就可以把用戶密碼作為 JWT 的密鑰。當(dāng)然,這也是用戶粒度級(jí)別的。這樣一來(lái),用戶修改密碼也就相當(dāng)于修改了密鑰。
為什么要使用JWT結(jié)構(gòu)化令牌
- JWT 的核心思想,就是用計(jì)算代替存儲(chǔ),有些 “時(shí)間換空間” 的 “味道”。當(dāng)然,這種經(jīng)過(guò)計(jì)算并結(jié)構(gòu)化封裝的方式,也減少了“共享數(shù)據(jù)庫(kù)” 因遠(yuǎn)程調(diào)用而帶來(lái)的網(wǎng)絡(luò)傳輸消耗,所以也有可能是節(jié)省時(shí)間的。
- 也是一個(gè)重要特性,是加密。因?yàn)?JWT 令牌內(nèi)部已經(jīng)包含了重要的信息,所以在整個(gè)傳輸過(guò)程中都必須被要求是密文傳輸?shù)模@樣被強(qiáng)制要求了加密也就保障了傳輸過(guò)程中的安全性。這里的加密算法,既可以是對(duì)稱加密,也可以是非對(duì)稱加密。
- 使用 JWT 格式的令牌,有助于增強(qiáng)系統(tǒng)的可用性和可伸縮性。這一點(diǎn)要怎么理解呢?我們前面講到了,這種 JWT 格式的令牌,通過(guò)“自編碼”的方式包含了身份驗(yàn)證需要的信息,不再需要服務(wù)端進(jìn)行額外的存儲(chǔ),所以每次的請(qǐng)求都是無(wú)狀態(tài)會(huì)話。這就符合了我們盡可能遵循無(wú)狀態(tài)架構(gòu)設(shè)計(jì)的原則,也就是增強(qiáng)了系統(tǒng)的可用性和伸縮性。
JWT令牌的生命周期。
- 令牌的自然過(guò)期過(guò)程,這也是最常見(jiàn)的情況。這個(gè)過(guò)程是,從授權(quán)服務(wù)創(chuàng)建一個(gè)令牌開(kāi)始,到第三方軟件使用令牌,再到受保護(hù)資源服務(wù)驗(yàn)證令牌,最后再到令牌失效。同時(shí),這個(gè)過(guò)程也不排除主動(dòng)銷毀令牌的事情發(fā)生,比如令牌被泄露,授權(quán)服務(wù)可以做主讓令牌失效。
- 訪問(wèn)令牌失效之后可以使用刷新令牌請(qǐng)求新的訪問(wèn)令牌來(lái)代替失效的訪問(wèn)令牌,以提升用戶使用第三方軟件的體驗(yàn)。
- 主動(dòng)發(fā)起令牌失效的請(qǐng)求,然后授權(quán)服務(wù)收到請(qǐng)求之后讓令牌立即失效。
復(fù)盤、成長(zhǎng),加油!