JWT Java项目使用
是后端开发最常用的方式。示例:完整配置标准 + 自定义 Claims。
还在为 JWT 的创建、签名和解析头疼?试试 JJWT 这个宝藏 Java 库!它是 JVM 和 Android 端超易用的 JWT 实现工具,纯 Java 开发、贴合 RFC 规范,把 JWT 的复杂逻辑全封装,靠构建器模式的简洁接口,新手也能快速上手,IDE 自动补齐还能大幅提升开发效率
Quickstart 快速入门
<!-- JJWT 核心 API -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.13.0</version>
</dependency>
<!-- 运行时实现 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>
<!-- JSON 序列化(Jackson) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>
大多数的JWT的复杂性都被隐藏在便捷且易读的 , 基于构建器模式的流畅接口的背后,这种设计非常适合依赖于IDE的自动补齐功能来快速编写代码

简单的不像话,短短两行代码就为我们生生成了jws,
JWS 就是 “带签名的 JWT”,签名的作用是防止 Token 被篡改,保证数据完整性。
在这个案例中,我们做了以下这些事:
1.构建了一个JWT, 将其注册声明(Subject/ 主题) 设置为ZJK
2.使用HS256算法的密钥对这个JWT进行签名
3.将其压缩拼接成最终的字符串形式
现在让我们验证 JWT(你应该始终丢弃签名与预期不符的 JWT)
assert Jwts.parser().verifyWith(key).build().parseSignedClaims(jws).getPayload().getSubject().equals("ZJK");
这行代码做了两件关键事:
1.使用之前生成的key验证 JWT 的签名 —— 如果签名验证失败(比如密钥不对、Token 被篡改),会抛出SignatureException(它继承自JwtException);
2.签名验证通过后,解析 JWT 的载荷(Claims),并断言(assert)其中的subject字段值是ZJK。不得不说,这种 “一拳搞定” 的单行代码太香了!
但如果解析或签名验证失败了怎么办?你可以捕获
JwtException并做相应处理:try { Jwts.parser().verifyWith(key).build().parseSignedClaims(compactJws); // 验证通过,我们可以信任这个JWT } catch (JwtException e) { // 验证失败,不要信任这个JWT! }
既然我们已经快速体验了如何创建和解析 JWT,接下来让我们深入讲解 JJWT 的 API。
创建 JWT 的标准步骤
1.调用Jwts.builder()创建JwtBuilder势力, 构建JWT的核心入口
2.[可选] 设置JWT头部header的自定义参数
3.调用构建器的方法设置载荷payload内容, 要么是Json声明(claims, 比如subject),要么是字节数组(byte[]),二者只能选其一
4.[可选] 调用signWith()签名, 或者encryptWith()加密方法 , 二者只能选其一
5.调用compact()方法最终生成紧凑的JWT字符串
载荷二选一:JWT 的 Payload 只能是「JSON Claims(如 subject/exp 等声明)」或「byte [] 二进制内容(需指定媒体类型)」,不能同时设置;
安全操作二选一:只能对 JWT 做「签名(signWith)」或「加密(encryptWith)」,不能既签名又加密;
无保护 JWT 警告:如果既不调用signWith()也不调用encryptWith(),生成的是「无保护 JWT(Unprotected JWT)」—— 这类 JWT 没有任何安全保障(可被任意篡改),生产环境绝对不能用!如需安全保障,必须在调用compact()前完成签名 / 加密。
JWT Header
JWT PayLoad
Payload 是 JWT 的核心数据载体,JJWT 支持两种类型(二选一),不能同时设置(否则compact()抛异常)。
类型 1:任意二进制内容(小众场景)
适用于传输文本、图片、文档等二进制数据,推荐使用content(byte[], 媒体类型)方法
import java.nio.charset.StandardCharsets;
public class JwtPayloadContentDemo {
public static void main(String[] args) {
SecretKey key = Jwts.SIG.HS256.key().build();
// 二进制内容(比如文本转字节)
byte[] content = "Hello, JJWT!".getBytes(StandardCharsets.UTF_8);
String jwt = Jwts.builder()
// 设置二进制Payload + 媒体类型(IANA标准,如text/plain、application/json)
.content(content, "text/plain")
.signWith(key)
.compact();
System.out.println("二进制Payload的JWT:" + jwt);
}
}
类型 2:JSON Claims(主流场景)
适用于身份认证、权限传递等场景,Payload 是 JSON 对象,支持标准 Claims和自定义 Claims,是后端开发最常用的方式。
示例:完整配置标准 + 自定义 Claims
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class JwtPayloadClaimsDemo {
public static void main(String[] args) {
SecretKey key = Jwts.SIG.HS256.key().build();
Date now = new Date();
Date expiration = new Date(now.getTime() + 3600 * 1000); // 1小时过期
Date notBefore = new Date(now.getTime() + 60 * 1000); // 1分钟后生效
// 批量自定义Claims(Map)
Map<String, Object> customClaims = new HashMap<>();
customClaims.put("name", "John Doe");
customClaims.put("admin", true);
String jwt = Jwts.builder()
// ===== 标准Claims(类型安全方法)=====
.issuer("https://my-app.com") // iss:签发人
.subject("1234567890") // sub:用户ID
.audience().add("web-admin").and() // aud:受众
.expiration(expiration) // exp:过期时间
.notBefore(notBefore) // nbf:生效时间
.issuedAt(now) // iat:签发时间
.id(UUID.randomUUID().toString()) // jti:唯一ID
// ===== 自定义Claims =====
.claim("age", 30) // 单个自定义Claim
.claims(customClaims) // 批量添加(Map)
.signWith(key)
.compact();
System.out.println("JSON Claims的JWT:" + jwt);
// 解析验证Claims
Claims payload = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(jwt)
.getPayload();
// 读取标准Claims
System.out.println("标准Claims - sub:" + payload.getSubject());
// 读取自定义Claims
System.out.println("自定义Claims - name:" + payload.get("name", String.class));
System.out.println("自定义Claims - admin:" + payload.get("admin", Boolean.class));
}
}
关键规则 & 最佳实践
- 二选一约束:
content()和claims()/subject()等不能同时用,否则抛异常; - 优先用标准方法:标准 Claims(如
subject()/expiration())优先用专用方法,比claim("sub", "xxx")更易读; - 自定义 Claims 注意:
claim()会覆盖同名 Key(比如先claim("age",20)再claim("age",30),最终是 30)。
1. Claims Map(声明映射)
如果希望一次性添加多个声明,可以使用JwtBuilder的claims(Map)方法:
Map<String,?> claims = getMyClaimsMap(); // 自行实现该方法
String jws = Jwts.builder()
.claims(claims)
// ... 其他配置 ...
2. JWT Compression(JWT 压缩)
如果你的 JWT 载荷(Payload)数据量较大(包含大量数据),你可能需要压缩 JWT 以减小其体积。请注意:这并非所有 JWT 的标准特性 —— 该特性仅针对 JWE(加密的 JWT)定义,且其他 JWT 库对于非 JWE 类型的令牌(token)可能不支持压缩。不过,JJWT 库同时支持对 JWS(签名的 JWT)和 JWE 进行压缩。
JWT Read(解析 JWT)
你可按如下步骤读取(解析)JWT:
- 调用
Jwts.parser()方法创建JwtParserBuilder实例; - 若预期要解析带签名或加密的 JWT,可选择性调用
keyLocator、verifyWith或decryptWith方法; - 调用
JwtParserBuilder的build()方法,创建并返回一个线程安全的JwtParser实例; - 根据预期的 JWT 类型,传入紧凑格式的 JWT 字符串,调用各类
parse*解析方法中的一种; - 将
parse*方法的调用逻辑包裹在 try/catch 代码块中,以防解析、签名验证或解密过程失败。
Jwt<?,?> jwt;
try {
jwt = Jwts.parser() // (1) 创建解析器构建器
.keyLocator(keyLocator) // (2) 动态定位签名/加密密钥
//.verifyWith(key) // 或使用固定密钥验证所有带签名的 JWT
//.decryptWith(key) // 或使用固定密钥解密所有加密的 JWT
.build() // (3) 构建解析器
.parse(compact); // (4) 也可使用 parseSignedClaims、parseEncryptedClaims、parseSignedContent 等方法
// 此时可安全信任该 JWT
} catch (JwtException ex) { // (5) 捕获异常
// 我们**无法**按创建者的预期使用该 JWT
}
注意:类型安全的 JWT若你确定解析器只会处理特定类型的 JWT
(例如,仅使用包含 Claims 载荷的带签名 JWT,或包含二进制内容载荷的加密 JWT 等),可调用对应的类型安全方法(如
parseSignedClaims、parseEncryptedClaims等),而非通用的
parse方法。这些parse*方法会返回你预期的类型安全 JWT 对象,例如
Jws<Claims>或Jwe<byte[]>,而非通用的
Jwt<?,?>实例。
固定解析密钥
若待解析的 JWT 是 JWS(带签名的 JWT)或 JWE(加密的 JWT),则必须使用密钥来验证签名或解密。如果是 JWS 且签名验证失败,或是 JWE 且解密失败,该 JWT 无法被安全信任,应直接丢弃。
那么我们该使用哪种密钥?
- 若解析的是 JWS,且该 JWS 是用
SecretKey(对称密钥)签名的,则需在JwtParserBuilder上指定相同的SecretKey。示例:Jwts.parser() .verifyWith(secretKey) // <---- 指定对称密钥 .build() .parseSignedClaims(jwsString); - 若解析的是 JWS,且该 JWS 是用
PrivateKey(非对称私钥)签名的,则需在JwtParserBuilder上指定该密钥对应的PublicKey(公钥),而非私钥。示例:Jwts.parser() .verifyWith(publicKey) // <---- 用公钥(而非私钥) .build() .parseSignedClaims(jwsString); - 若解析的是 JWE,且该 JWE 是通过直接加密方式、使用
SecretKey(对称密钥)加密的,则需在JwtParserBuilder上指定相同的SecretKey。示例:Jwts.parser() .decryptWith(secretKey) // <---- 也可使用 Keys.password(charArray) 生成的密码密钥 .build() .parseEncryptedClaims(jweString); - 若解析的是 JWE,且该 JWE 是通过密钥算法、使用
PublicKey(非对称公钥)加密的,则需在JwtParserBuilder上指定该密钥对应的PrivateKey(私钥),而非公钥。示例:Jwts.parser() .decryptWith(privateKey) // <---- 用私钥(而非公钥) .build() .parseEncryptedClaims(jweString);
Sign 签名密钥
如果您不想考虑密钥的位长度要求,或是想简化开发流程,JJWT 提供了便捷的构建器类,可为您想要使用的任意 JWT 签名算法生成足够安全的密钥。
对称密钥(Secret Keys)
如果您希望生成一个足够安全的 SecretKey(对称密钥),用于 JWT 的 HMAC-SHA 系列算法(如 HS256/HS384/HS512),请使用对应算法的 key() 构建方法:
SecretKey key = Jwts.SIG.HS256.key().build(); // 也可使用 HS384.key() 或 HS512.key()
在底层实现中,JJWT 会使用 JCA(Java Cryptography Architecture)默认提供程序的 KeyGenerator,为指定算法生成符合最小长度要求的安全随机密钥(比如 HS256 会自动生成 256 位 / 32 字节密钥)。
若您希望在密钥生成时指定特定的 JCA 提供程序(Provider)或安全随机数生成器(SecureRandom),可将其作为构建器参数传入。例如:
SecretKey key = Jwts.SIG.HS256.key().provider(aProvider).random(aSecureRandom).build();
如果需要保存这个新生成的 SecretKey,您可以对其进行 Base64(或 Base64URL)编码:
String secretString = Encoders.BASE64.encode(key.getEncoded());
注意:务必将生成的 secretString 保存在安全位置 ——Base64 编码并非加密,因此该字符串仍属于敏感信息。例如,您可以在将其保存到磁盘前对其进行进一步加密处理。
Encoders.BASE64.encode(key.getEncoded())的核心是把二进制密钥转成可存储的字符串;- 这么做的目的是解决二进制密钥无法配置、无法传输的问题;
- 重点记住:Base64 编码不是加密,密钥字符串必须安全存储,使用时需要解码还原。
非对称密钥(Asymmetric Keys)
如果您希望生成足够安全的椭圆曲线(ECDSA)或 RSA 非对称密钥对,用于 JWT 的 ECDSA 或 RSA 系列算法(如 RS256/RS384/RS512/PS256 等),请使用对应算法的 keyPair() 构建方法:
KeyPair keyPair = Jwts.SIG.RS256.keyPair().build(); // 也可使用 RS384、RS512、PS256 等算法
生成 KeyPair(密钥对)后,您可以使用其中的私钥(keyPair.getPrivate())创建带签名的 JWS(即签名 JWT),并使用公钥(keyPair.getPublic())解析 / 验证该 JWS。
如何从Claims中拿到事先存好的数据呢?
核心前提:存入和取出的「key 必须一致」
首先明确:你往 JWT 里存数据时用的key(比如userId/username/role),就是取出时的唯一标识。
方式 1:指定类型取值(推荐,最安全)
Claims.get(String key, Class<T> type):直接指定要获取的数据类型,避免手动强转,类型不匹配时会抛清晰的异常,便于排查。
方式 2:基础取值(需手动强转,不推荐)
Claims.get(String key):返回Object类型,需要手动强转,类型不匹配时会抛ClassCastException(异常信息不清晰)。
方式 3:带默认值取值(避免空指针)
Claims.getOrDefault(String key, T defaultValue):如果 key 不存在,返回指定的默认值,适合非必填字段。

更多推荐

所有评论(0)