1 場景
JSON Web Token (JWT)是一種開放標準(RFC 7519)代箭,它定義了一種緊湊和自包含的方式,用于作為JSON對象
在各方之間安全地傳輸信息
济竹。這個信息可以被驗證和信任胞枕,因為它是數(shù)字簽名的
柴梆。JWTs可以使用密鑰
(使用HMAC算法)或使用RSA或ECDSA的公鑰/私鑰
進行簽名攀操。
官網(wǎng):https://jwt.io/
2 說明
2.1 結(jié)構(gòu)
在其緊湊的形式中金矛,JSON Web令牌由點(.
)分隔的三個部分組成拍埠,它們是:
- Header
- Payload
- Signature
因此阁吝,JWT通常如下所示:
xxxxx.yyyyy.zzzzz
即,如下格式:
Header.Payload.Signature
2.2 組成
2.2.1 Header
Header通常由兩部分組成:令牌的類型
械拍,即JWT突勇,以及所使用的簽名算法
,如HMAC SHA256或RSA坷虑。
如下:
{
"alg": "HS256",
"typ": "JWT"
}
然后甲馋,該JSON是Base64Url編碼
的,以形成JWT的第一部分迄损。
2.2.2 Payload
令牌的第二部分是Payload定躏,它包含聲明claims
。聲明是關(guān)于實體(通常是用戶
)和附加數(shù)據(jù)
的聲明芹敌。聲明claims
有三種類型:registered痊远、public、和private 氏捞。
(1)registered
registered類型的claims碧聪。
這些是一組預定義claims
,它們不是強制性的液茎,而是推薦的
逞姿,以提供一組有用的、可互操作的claims捆等。其中包括:iss(發(fā)行人)滞造、exp(到期時間)、sub(主題)栋烤、aud(目標受眾)等谒养。
注意,聲明名claims只有
三個字符長
明郭,因為JWT是為了緊湊买窟。
如:iss、exp达址、sub蔑祟、aud
(2)public
這些可以由使用JWTs的人隨意定義
。但是為了避免沖突沉唠,應該在 IANA JSON Web Token Registry 令牌注冊表中定義它們,或者將它們定義為包含抗沖突名稱空間的URI苛败。
在 IANA JSON Web Token Registry 中定義的claimsName信息如下满葛,定義的為默認定義的claimName的含義:
即如果定義的Claim Name径簿,為避免沖突,需參照IANA JSON Web Token Registry中定義的ClaimName嘀韧,如需`定義其他ClaimName篇亭,需不要和這里定義的ClaimName沖突。
可以在此部分配置定義的要傳遞的業(yè)務信息
锄贷,如:用戶信息译蒂、部門信息、角色信息等谊却。
(3)private
這些自定義claims是為了在同意使用它們的各方之間共享信息
而創(chuàng)建的柔昼,它們既不是registered,也不是public的claims炎辨。
一個有效的Payload的例子如下:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后對claims進行Base64Url
編碼捕透,以形成JSON Web令牌的第二部分
。
請注意碴萧,對于已簽名的令牌乙嘀,該信息雖然受到保護,不會被篡改破喻,但任何人都可以讀懂虎谢。
不要將機密信息放在JWT的有效負載或頭元素中
,除非它被加密了
曹质。
2.2.3 Signature
要創(chuàng)建簽名Signature
部分嘉冒,必須獲取已編碼的Header
、已編碼的Payload
咆繁、head中指定的密鑰
讳推、head中指定的算法
。
根據(jù)獲取的上述信息玩般,生成簽名Signature银觅。
例如,使用HMAC SHA256算法時坏为,簽名的生成方式如下:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
即格式如下:
head算法xxx(base64UrlEncode(json格式的header) + "." +base64UrlEncode(json格式的header),head密鑰xxx)
簽名用于驗證消息
在整個過程中沒有被篡改
究驴,而且,在使用私鑰簽名的令牌的情況下匀伏,它還可以驗證JWT的發(fā)送方是它所聲稱的那個人
洒忧。
2.2.4 匯總
輸出是三個用點分隔的Base64-URL字符串,它們可以很容易地在HTML和HTTP環(huán)境中傳遞够颠,同時與基于xml的標準(如SAML)相比更緊湊熙侍。
下面展示了一個JWT,它對前面的頭和有效負載進行了編碼,并使用secret對其進行了簽名蛉抓。
2.3 在線調(diào)試
JWT的在線調(diào)試驗證地址: jwt.io Debugger
如下:
3 Java實現(xiàn)
這里使用java-jwt來實現(xiàn)JWT的操作庆尘。
3.1 maven依賴
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.14.0</version>
</dependency>
3.2 工具類封裝
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* JWT工具類
*/
public class JWTUtil {
/**
* 生成簽名
* @param claimMap claimMap
* @param secret 密鑰
* @param expireMilliSecond 過期時間-毫秒(如果為null,則無過期時間)
* @return
*/
public static String sign(Map<String, String> claimMap, String secret, Long expireMilliSecond) {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTCreator.Builder builder = JWT.create();
if (claimMap != null && claimMap.size() > 0) {
for (Map.Entry<String, String> entry : claimMap.entrySet()) {
String key = entry.getKey();
if (key == null || key.equals("")) {
continue;
}
builder.withClaim(key, entry.getValue());
}
}
if (expireMilliSecond != null) {
builder.withExpiresAt(new Date(System.currentTimeMillis() + expireMilliSecond));
}
return builder.sign(algorithm);
}
/**
* 驗證token
* @param token token
* @param secret 密鑰
* @return
*/
public static DecodedJWT verify(String token, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
Verification verification = JWT.require(algorithm);
JWTVerifier verifier = verification.build();
DecodedJWT jwt = verifier.verify(token);
return jwt;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 獲取JWT中內(nèi)容
* @param jwt jwt
* @param claimName claim名稱
* @return
*/
public static String getClaimValueByJwt(DecodedJWT jwt, String claimName) {
return jwt.getClaim(claimName).asString();
}
/**
* 獲取token中內(nèi)容
* @param token token
* @param claimName claim名稱
* @return
*/
public static String getClaimValueByToken(String token, String claimName) {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(claimName).asString();
}
}
3.3 使用示例
3.3.1 代碼
public static void main(String[] args) throws Exception {
// ====================【參數(shù)定義】====================
// (1)密鑰
String secret = "x123456";
// (2)自定義claim內(nèi)容
Map<String, String> claimMap = new HashMap<>();
claimMap.put("name", "張三");
claimMap.put("roleId", "1");
// (3)超時時間-3小時(單位:毫秒)
Long expireSecond = 3 * 60 * 60 * 1000L;
System.out.println("====================【生成token】====================");
String token = JWTUtil.sign(claimMap, secret, expireSecond);
System.out.println("[生成-token]:" + token);
System.out.println("\n====================【驗證token巷送,并獲取自定義屬性】====================");
DecodedJWT jwt = JWTUtil.verify(token, secret);
boolean verifyResult = jwt == null ? false : true;
System.out.println("[token驗證結(jié)果]:" + verifyResult);
System.out.println("[通過驗證結(jié)果驶忌,獲取自定義屬性]:name--" + JWTUtil.getClaimValueByJwt(jwt, "name"));
System.out.println("\n====================【獲取自定義屬性】====================");
System.out.println("[直接通過token,獲取自定義屬性]:name--" + JWTUtil.getClaimValueByToken(token, "name"));
}
3.3.2 輸出結(jié)果
====================【生成token】====================
[生成-token]:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlSWQiOiIxIiwibmFtZSI6IuW8oOS4iSIsImV4cCI6MTYxNTA0NjU3N30.ogV3U3dDXdo1hfZBpdr0FxvBbfjOedabNCHZZKLA2Yo
====================【驗證token笑跛,并獲取自定義屬性】====================
[token驗證結(jié)果]:true
[通過驗證結(jié)果付魔,獲取自定義屬性]:name--張三
====================【獲取自定義屬性】====================
[直接通過token,獲取自定義屬性]:name--張三
3.3.3 官網(wǎng)校驗
去jwt在線調(diào)試網(wǎng)站上校驗: jwt.io Debugger