Appearance
JWT介绍
本文主要介绍JWT,是Spring Security系列第四篇文章,前三篇文章如下:
1. 什么是JWT
1.1 介绍
JWT全称JSON Web Tokens,是以json为基础格式的,经过签名后的可信凭证。 参考网站:https://jwt.io/
JWT由三部分组成,使用.分隔:
- Header (头部): 包含令牌的类型和使用的哈希算法。
- Payload (负载): 包含声明(例如用户 ID、过期时间等)。
- Signature (签名): 用于验证令牌的完整性和真实性。
1.2 生成JWT的过程
生成JWT的过程如下:
- 首先创建头部,确定令牌的类型和使用的哈希算法
json
{
"alg": "HS256",
"typ": "JWT"
}- 然后创建负载,包含一些声明,例如用户ID、过期时间等。
json
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}在JWT中,预定义了一些声明名称,它们具有特殊含义:
- iss (Issuer): 签发者的标识。它标识了 JWT 的签发方。例如,可以是一个公司或应用程序的名称。
- sub (Subject): 主题的标识。它标识了 JWT 所面向的用户或实体。例如,可以是一个用户的 ID。
- aud (Audience): 接收者的标识。它标识了 JWT 的预期接收方。例如,可以是一个或多个应用程序的名称。
- exp (Expiration Time): 过期时间。它定义了 JWT 的过期时间,超过该时间后,JWT 将失效。这是一个重要的安全措施,用于限制 JWT 的有效期限。该值是一个 Unix 时间戳(自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数)。
- nbf (Not Before): 生效时间。它定义了 JWT 的生效时间,在该时间之前,JWT 不应被接受处理。该值也是一个 Unix 时间戳。
- iat (Issued At): 签发时间。它定义了 JWT 的签发时间。该值也是一个 Unix 时间戳。
- jti (JWT ID): JWT 的唯一标识符。它为 JWT 提供了一个唯一的标识符,可以用于防止重放攻击。
参考资料:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
除了预定义的声明,我们也可以声明自己的数据,例如上面的"name":"John Doe"。
- 最后使用指定的哈希算法对头部和载荷签名
- 首先使用Base64URL分别对头部和载荷进行编码,然后用
.号连接起来; - 然后使用指定的哈希算法,用密钥对编码后的头部和载荷进行签名,最后使用Base64URL对签名结果编码,使用
.将签名结果拼接到头部和载荷后,形成JWT。
- 首先使用Base64URL分别对头部和载荷进行编码,然后用
示例如下: 
2. 在Java中使用生成JWT
本小节介绍如何在Java中自己生成JWT,以HmacSHA256哈希算法为例。
java
void testGenerateJWT(){
// 1. 准备头部和载荷
String header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
String payload = "{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}";
// 2. 将头部和载荷进行Base64UrlEncoded编码
String headerBase64 = Base64.getUrlEncoder()
.encodeToString(header.getBytes(StandardCharsets.UTF_8))
.replaceAll("=","");
String payloadBase64 = Base64.getUrlEncoder()
.encodeToString(payload.getBytes(StandardCharsets.UTF_8))
.replaceAll("=","");
// 3. 使用 HMAC SHA256 算法生成签名
String data = headerBase64 + "." + payloadBase64;
String key = "your-256-bit-secret";
String signature = hmacSha256(data, key);
// 4. 生成JWT
String jwt = data + "." +signature;
Assertions.assertEquals("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",jwt);
}
// 签名算法
public static String hmacSha256(String data, String key) {
try {
// 1. 创建 SecretKeySpec 对象,指定密钥和算法
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
// 2. 创建 Mac 对象,指定算法
Mac mac = Mac.getInstance("HmacSHA256");
// 3. 使用密钥初始化 Mac 对象
mac.init(secretKeySpec);
// 4. 执行 MAC 操作,获取哈希值
byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 5. 将哈希值转换为Base64UrlEncoded字符串
return Base64.getUrlEncoder().encodeToString(hmacBytes).replaceAll("=","");
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return null;
}
}上面程序生成的JWT与下面相同:

注意点:
JWT 使用了一种称为 Base64URL 的编码方式,它是 Base64 的变体。这种变体是为了使 JWT 更安全地在 URL 中传输。
Base64 与 Base64URL 的区别
- Base64: 使用 64 个字符(A-Z, a-z, 0-9, +, /)以及填充字符“=”来编码二进制数据。
- Base64URL: 是 Base64 的一种修改版本,主要针对 URL 传输进行了优化。它使用 64 个字符(A-Z, a-z, 0-9, - , _),并且省略了填充字符“=”。(
+被替换为了-,/被替换为了_)。
3. 使用java-jwt库生成JWT
由于Java生态的丰富,我们不用自己从零开始编写代码生成JWT,可以使用java-jwt库来帮助我们生成JWT。
地址:https://github.com/auth0/java-jwt
首先在项目中引入依赖:
xml
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>然后就可以使用java-jwt库中提供的工具类来生成JWT了。
生成JWT代码:
java
void testGenJWT(){
try {
Algorithm algorithm = Algorithm.HMAC256("your-256-bit-secret");
String token = JWT.create()
.withSubject("1234567890")
.withClaim("name","John Doe")
.withIssuedAt(Instant.ofEpochSecond(1516239022))
.sign(algorithm);
System.out.println(token);
} catch (JWTCreationException exception){
exception.printStackTrace();
}
}校验并解析JWT的代码(这里主要是解析载荷信息):
java
void testValidJWT(){
try {
String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
Algorithm algorithm = Algorithm.HMAC256("your-256-bit-secret");
JWTVerifier verifier = JWT.require(algorithm).build();
// 校验JWT是否被修改,如果校验不通过,则会抛异常 JWTDecodeException
DecodedJWT decodedJWT = verifier.verify(jwt);
// 获取JWT中的载荷信息
Map<String, Claim> claims = decodedJWT.getClaims();
claims.forEach((k,v)->{
System.out.println(k + " : " + v.toString());
});
}catch (Exception e){
e.printStackTrace();
}
}