JWT | 一分鐘掌握J(rèn)WT | 概念及實(shí)例
什么是JWT
JWT的全稱是Json Web Token。是基于RFC 7519開(kāi)放標(biāo)準(zhǔn)的,它定義了一種緊湊且獨(dú)立的方式,用于在各方之間以 JSON 對(duì)象的形式安全地傳輸信息。此信息可以用作驗(yàn)證和相互信任,因?yàn)樗墙?jīng)過(guò)數(shù)字簽名的。JWT 可以使用密鑰(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公鑰/私鑰對(duì)進(jìn)行簽名。
哪里能用JWT
以下是JWT兩種使用場(chǎng)景:
授權(quán)
:這是使用 JWT 的最常見(jiàn)的使用場(chǎng)景。用戶登錄后,每個(gè)后續(xù)請(qǐng)求都將包含 JWT,允許用戶訪問(wèn)使用該令牌允許的路由、服務(wù)和資源。單點(diǎn)登錄是當(dāng)今廣泛使用 JWT 的一項(xiàng)功能,因?yàn)樗拈_(kāi)銷(xiāo)很小,并且能夠跨不同域輕松使用。信息交換
:JWT是在各方之間安全傳輸信息的比較便捷的方式。由于 JWT 可以簽名(例如,使用公鑰/私鑰對(duì)),因此可以確定發(fā)送者是否是在您的授權(quán)范圍之內(nèi)。并且,由于簽名是使用標(biāo)頭和有效負(fù)載計(jì)算的,因此還可以驗(yàn)證內(nèi)容是否未被篡改。
JWT的組成
這是一個(gè)JWT的token串:
java復(fù)制代碼eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
很復(fù)雜,看不懂是不是?其實(shí)這一串是經(jīng)過(guò)加密之后的密文字符串,中間通過(guò).
來(lái)分割。每個(gè).
之前的字符串分別表示JWT的三個(gè)組成部分:Header
、Payload
、Signature
。
Header(頭信息)
Header的主要作用是用來(lái)標(biāo)識(shí)。通常是兩部分組成:
typ
:type 的簡(jiǎn)寫(xiě),令牌類(lèi)型,也就是JWT。alg
:Algorithm 的簡(jiǎn)寫(xiě),加密簽名算法。一般使用HS256,jwt官網(wǎng)提供了12種的加密算法,截圖如下:
Header的明文示例:
json復(fù)制代碼{ ??"alg": "HS256", ??"typ": "jwt" }
經(jīng)過(guò)Base64編碼之后的明文,變?yōu)椋?/p>
java復(fù)制代碼eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9
也就是第一個(gè).
之前的密文串。以下是Header部分常用部分的聲明:
keyname說(shuō)明typ令牌類(lèi)型如果存在,則必須將其設(shè)置為已注冊(cè)的 IANA 媒體類(lèi)型。cty內(nèi)容類(lèi)型如果使用嵌套簽名或加密,建議將其設(shè)置為 ;否則,請(qǐng)省略此字段。alg消息身份驗(yàn)證代碼算法發(fā)行者可以自由設(shè)置算法來(lái)驗(yàn)證令牌上的簽名。但是,某些受支持的算法不安全。kid密鑰標(biāo)識(shí)指示客戶端用于生成令牌簽名的密鑰的提示。服務(wù)器將此值與文件上的密鑰匹配,以驗(yàn)證簽名是否有效以及令牌是否真實(shí)。x5cx.509 證書(shū)鏈RFC4945 格式的證書(shū)鏈,對(duì)應(yīng)于用于生成令牌簽名的私鑰。服務(wù)器將使用此信息來(lái)驗(yàn)證簽名是否有效以及令牌是否真實(shí)。x5ux.509 證書(shū)鏈網(wǎng)址服務(wù)器可在其中檢索與用于生成令牌簽名的私鑰對(duì)應(yīng)的證書(shū)鏈的 URL。服務(wù)器將檢索并使用此信息來(lái)驗(yàn)證簽名是否真實(shí)。crit危急服務(wù)器必須理解的標(biāo)頭列表,以便接受令牌為有效令牌
Payload(負(fù)載信息)
也稱為JWT claims,放置需要傳輸?shù)男畔ⅲ腥?lèi):
保留claims
:主要包括iss發(fā)行者、exp過(guò)期時(shí)間、sub主題、aud用戶等。公共claims
:定義新創(chuàng)的信息,比如用戶信息和其他重要信息。私有claims
:用于發(fā)布者和消費(fèi)者都同意以私有的方式使用的信息。
以下是Payload的官方定義內(nèi)容:
keyname說(shuō)明iss發(fā)送者標(biāo)識(shí)頒發(fā) JWT 的發(fā)送主體。sub主題標(biāo)識(shí) JWT 的主題。aud接收者標(biāo)識(shí) JWT 所針對(duì)的接收者。每個(gè)在處理 JWT 的主體都必須使用受眾聲明中的值來(lái)標(biāo)識(shí)自己。如果處理的主體在存在此聲明時(shí)未將自己標(biāo)識(shí)為聲明中的值,則必須拒絕 JWT。exp到期時(shí)間標(biāo)識(shí)不得接受 JWT 進(jìn)行處理的過(guò)期時(shí)間。該值必須是日期類(lèi)型,而且是1970-01-01 00:00:00Z 之后的日期秒。nbfjwt的開(kāi)始處理的時(shí)間標(biāo)識(shí) JWT 開(kāi)始接受處理的時(shí)間。該值必須是日期。iatjwt發(fā)出的時(shí)間標(biāo)識(shí) JWT 的發(fā)出的時(shí)間。該值必須是日期。jtijwt id令牌的區(qū)分大小寫(xiě)的唯一標(biāo)識(shí)符,即使在不同的頒發(fā)者之間也是如此。
Payload明文示例:
json復(fù)制代碼{ ??"sub": "12344321", ??"name": "Mars醬", // 私有claims ??"iat": 1516239022 }
經(jīng)過(guò)Base64加密之后的明文,變?yōu)椋?/p>
java復(fù)制代碼eyJzdWIiOiIxMjM0NDMyMSIsIm5hbWUiOiJNYXJz6YWxIiwiaWF0IjoxNTE2MjM5MDIyfQ
也就是第一個(gè).
和第二個(gè).
之間的密文串內(nèi)容。
Signatrue(簽名信息)
Signature 部分是對(duì)Header和Payload兩部分的簽名,作用是防止 JWT 被篡改。這個(gè)部分的生成規(guī)則主要是是公式(偽代碼)是:
java復(fù)制代碼Header中定義的簽名算法( ?? ?base64編碼(header) + "." + base64編碼(payload), ?? ?secret )
secret
是存放在服務(wù)端加密使用到的鹽。
得到簽名之后,把Header的密文、Payload的密文、Signatrue的密文按順序拼接成為一個(gè)字符串,中間通過(guò).
來(lái)連接并分割,整個(gè)串就是JWT了。
JWT實(shí)例
概念講完了,我們來(lái)個(gè)實(shí)例吧,先來(lái)一個(gè)jwt的編碼:
java復(fù)制代碼 ? ?public static String encodeJWT(String key) { ?? ? ? ?// 1. 定義header部分內(nèi)容 ? ? ? ? ?Map headerMap = new HashMap(); ?? ? ? ?headerMap.put("alg", SignatureAlgorithm.HS256.getValue()); ?? ? ? ?headerMap.put("typ", "JWT"); ? ? ? ? ?// 2. 定義payload部分內(nèi)容 ?? ? ? ?Map payloadMap = new HashMap(); ?? ? ? ?payloadMap.put("sub", "mars醬讓你爽一分鐘"); ?? ? ? ?payloadMap.put("iat", UUID.randomUUID()); ?? ? ? ?payloadMap.put("exp", System.currentTimeMillis() + 24 * 60 * 60 * 1000); ?? ? ? ?payloadMap.put("name", "Mars醬"); ?? ? ? ?payloadMap.put("role", "醬油王"); ? ? ? ? ?// 3.生成token ?? ? ? ?String jwtToken = Jwts.builder() ?? ? ? ? ? ? ? ?.setHeaderParams(headerMap) ?? ? ? ? ? ? ? ?.setClaims(payloadMap) ?? ? ? ? ? ? ? ?.signWith(SignatureAlgorithm.HS256, key) ?? ? ? ? ? ? ? ?.compact(); // 拼接header + payload // ? ? ? ?System.out.println(jwtToken); ?? ? ? ?return jwtToken; ?? ?}
我們?cè)趍ain函數(shù)中調(diào)用這個(gè),運(yùn)行得到結(jié)果:
很長(zhǎng)的密文字符串,就不截完整了。其中key是鹽,jsonwebtoken的jar包規(guī)定,key必須字節(jié)數(shù)要大于等于你所用的加密算法的最小字節(jié)數(shù),mars醬這里使用的是HS256,最小的key長(zhǎng)度規(guī)定的就是256:
因此,key值我這里傳入了一個(gè)超長(zhǎng)的key:
java復(fù)制代碼String key = "marsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmars";
如果你的key沒(méi)這么長(zhǎng),你可能會(huì)報(bào)這樣錯(cuò)誤:
下面再來(lái)一個(gè)解密jwt的代碼,入?yún)榧用芎蟮膉wt和鹽key:
java復(fù)制代碼 ? ?public static void decodeJWT(String jwtToken, String key) { ?? ? ? ?try { ?? ? ? ? ? ?Claims claims = Jwts.parser() ?? ? ? ? ? ? ? ? ? ?.setSigningKey(key) ?? ? ? ? ? ? ? ? ? ?.parseClaimsJws(jwtToken) ?? ? ? ? ? ? ? ? ? ?.getBody(); ?? ? ? ? ? ?Object sub = claims.get("sub"); ?? ? ? ? ? ?Object name = claims.get("name"); ?? ? ? ? ? ?Object role = claims.get("role"); ?? ? ? ? ? ?Object exp = claims.get("exp"); ? ? ? ? ? ? ?System.out.println("sub:" + sub + "\nname:" + name.toString() + "\nrole:" + role + "\nexp:" + exp + "\n失效了?" + ((System.currentTimeMillis() - (Long)exp) > 0)); ?? ? ? ?} catch (ExpiredJwtException e) { ?? ? ? ? ? ?System.out.println("mars醬提醒您:token已過(guò)期"); ?? ? ? ?} ?? ?}
在main中調(diào)用后,得到結(jié)果:
把生成的jwt字符串放入官網(wǎng)( jwt.io )的解密界面,和程序解密的結(jié)果一樣,是不是很完美?
優(yōu)缺點(diǎn)
好了,概念說(shuō)完了,實(shí)例也給了,想想jwt有什么優(yōu)缺點(diǎn)?我總結(jié)了一下:
優(yōu)點(diǎn):
可擴(kuò)展性好
應(yīng)用程序分布式部署的情況下,如果使用session機(jī)制,那就要要做多臺(tái)機(jī)器的數(shù)據(jù)共享,通??梢源嬖跀?shù)據(jù)庫(kù)或者redis里面。而使用jwt不需要共享。jwt是無(wú)狀態(tài)的
jwt不在服務(wù)端存儲(chǔ)任何狀態(tài)。RESTful API的原則之一是無(wú)狀態(tài),發(fā)出請(qǐng)求時(shí),總會(huì)返回帶有參數(shù)的響應(yīng),不會(huì)產(chǎn)生附加影響。用戶的認(rèn)證狀態(tài)引入這種附加影響,這破壞了這一原則。另外jwt的載荷中可以存儲(chǔ)一些常用信息,用于交換信息,有效地使用 JWT,可以降低服務(wù)器查詢數(shù)據(jù)庫(kù)的次數(shù)。
缺點(diǎn):
安全性
由于jwt的payload是使用base64編碼的,并沒(méi)有加密,因此jwt中不能存儲(chǔ)敏感數(shù)據(jù)。而session的信息是存在服務(wù)端的,相對(duì)來(lái)說(shuō)更安全。一次性
無(wú)狀態(tài)是jwt的特點(diǎn),但也導(dǎo)致了這個(gè)問(wèn)題,jwt是一次性的。想修改里面的內(nèi)容,就必須簽發(fā)一個(gè)新的jwt。
jwt開(kāi)源框架
mars醬這里使用的jwt是io.jsonwebtoken的,它需要在pom中引入依賴:
xml復(fù)制代碼 <!-- jwt api定義 --> ?<dependency> ?<groupId>io.jsonwebtoken</groupId> ?<artifactId>jjwt-api</artifactId> ?<version>0.10.2</version> ?</dependency> ?<!-- jwt api impl實(shí)現(xiàn) --> ?<dependency> ?<groupId>io.jsonwebtoken</groupId> ?<artifactId>jjwt-impl</artifactId> ?<version>0.10.2</version> ?</dependency> ?<!-- jwt json --> ?<dependency> ?<groupId>io.jsonwebtoken</groupId> ?<artifactId>jjwt-jackson</artifactId> ?<version>0.10.2</version> ?</dependency>
其余的jwt框架全在jwt官網(wǎng)列舉了?;靖采w了全語(yǔ)言
好好享受jwt的校驗(yàn)過(guò)程吧