引言
JWT(JSON Web Token)是一种基于JSON的、用于双方之间安全传输信息的简洁的、URL安全的令牌标准。在现代Web应用程序中,JWT作为一种高效且安全的认证机制,被广泛应用于用户身份验证和信息交换场景。本文旨在详细介绍JWT Token的生成与解析过程,为开发者提供一份专业的技术参考。JJWT简介
JJWT是一个开源的Java库,专门用于处理JSON Web Tokens(JWT)。JWT是一种基于JSON的、用于双方之间安全传输信息的简洁的、URL安全的令牌标准(RFC 7519)。通过JJWT,开发者可以轻松地在Java应用程序中生成、解析和验证JWT。JWT结构概述
JWT由三个部分组成,它们通过“.”分隔符连接而成:Header(头部)、Payload(负载)和Signature(签名)。- Header:包含令牌的元数据,如签名算法和令牌类型,通常采用Base64Url编码。
- Payload:包含实际需要传递的数据,如用户ID、过期时间等官方字段,同样采用Base64Url编码。
- Signature:对Header和Payload进行签名,以确保数据的完整性和来源的真实性。签名通常使用指定的算法和密钥生成。
添加依赖
要在Java项目中使用JJWT,首先需要在项目的`pom.xml` 文件中添加相应的依赖。以下是一个Maven依赖的示例:<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
</dependency>
生成Token
使用JJWT生成Token,首先,需要创建一个` Claims` 对象来设置Token的负载信息。然后,使用` Jwts.builder()` 方法构建Token,并指定签名算法和签名密钥。以下是一个生成Token的示例代码:/**
* 生成JWT(JSON Web Token)
*
* @param key 用于签名的密钥
* @return 生成的JWT字符串
*/
public static String generateToken(Key key) {
// 设置JWT的过期时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
long expMillis = nowMillis + EXPIRATION_TIME; // 1小时后过期
Date exp = new Date(expMillis);
// 创建 claims
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "subject"); // 设置JWT的主题
claims.put("name", "Shore");
// 生成JWT
return Jwts.builder()
.id(getUUID())
.subject("subject")
.claims(claims) // 设置JWT的主题
.issuer("issuer")
.issuedAt(now) // 设置JWT的签发时间
.expiration(exp) // 设置JWT的过期时间
.signWith(key) // 使用算法签名JWT
.audience() // 设置JWT的接收者
.add("audience")
.and()
.header()
.add("alg", "HS256")
.and()
.compact(); // 生成紧凑的JWT字符串
}
在上述代码中,我们创建了一个包含用户ID(<font style="color:rgb(51, 51, 51);">sub</font>
字段)的负载,并设置了Token的过期时间。然后,使用HS256算法和指定的密钥对Token进行签名,并生成最终的Token字符串。
校验Token
要校验Token,需要使用`Jwts.parser()` 方法来构建一个解析器,并设置签名密钥,根据解析过程是否抛出异常来判断 token 是否有效。以下是一个校验Token的示例:/**
* 验证JWT(JSON Web Token)的有效性
* 此方法使用提供的密钥对JWT进行验证,以确保其未被篡改且有效
* 它支持使用SecretKey(对称密钥)或PublicKey(非对称密钥)进行验证
*
* @param token 待验证的JWT字符串
* @param key 用于验证JWT的密钥,可以是SecretKey或PublicKey实例
* @return 如果JWT有效则返回true,否则返回false
*/
public static boolean validateToken(String token, Key key) {
try {
// 检查token和密钥是否为空或无效
if (StringUtils.isBlank(token) || Objects.isNull(key)) {
return false;
}
// 根据密钥类型验证JWT
if (key instanceof SecretKey) {
// 使用SecretKey验证JWT
Jwts.parser().verifyWith((SecretKey) key).build().parseSignedClaims(token);
} else if (key instanceof PublicKey) {
// 使用PublicKey验证JWT
Jwts.parser().verifyWith((PublicKey) key).build().parseSignedClaims(token);
}
// 验证成功,返回true
return true;
} catch (Exception e) {
// 验证失败,输出错误信息并返回false
System.err.println("Failed to validate token: " + e.getMessage());
return false;
}
}
上述代码可以接收不同方式加密的token,对称或者非对称加密,根据不同的类型尝试解析来验证token。
解析Token
要解析Token,你需要使用`Jwts.parser()` 方法来构建一个解析器,并设置签名密钥。以下是一个解析Token的示例:/**
* 解析JWT令牌,返回令牌中的声明信息
*
* @param token 待解析的JWT令牌字符串
* @param key 用于验证令牌签名的密钥,可以是SecretKey或PublicKey实例
* @return 返回Claims对象,包含令牌中的所有声明如果解析失败或密钥类型不匹配,则返回null
*/
public static Claims parseToken(String token, Key key) {
try {
// 检查token和密钥是否为空或无效
if (StringUtils.isBlank(token) || Objects.isNull(key)) {
return null;
}
// 根据密钥类型解析令牌
if (key instanceof SecretKey) {
// 使用SecretKey解析令牌
return Jwts.parser().verifyWith((SecretKey) key).build().parseSignedClaims(token).getPayload();
} else if (key instanceof PublicKey) {
// 使用PublicKey解析令牌
return Jwts.parser().verifyWith((PublicKey) key).build().parseSignedClaims(token).getPayload();
} else {
// 不支持的密钥类型
return null;
}
} catch (Exception e) {
// 其他异常,比如token格式不正确、解析失败等
System.err.println("Failed to parse token: " + e.getMessage());
return null; // token无效
}
}
在上述代码中,我们解析不同方式加密的Token,如果解析失败则返回null,成功则返回Claims对象。
加密方式
对称加密
使用 KeyPairGenerator 提供的方法生成对称密钥。以下是生成对称密钥的代码示例:/**
* 生成对称密钥(AES)
*
* @param keyLength 密钥长度(如:128, 192, 256)
* @return 生成的对称密钥的Base64编码字符串
* @throws NoSuchAlgorithmException 如果指定的加密算法不可用
*/
public static String generateSymmetricKey(int keyLength) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(keyLength);
return Base64.getEncoder().encodeToString(keyGenerator.generateKey().getEncoded()) ;
}
有了对称密钥以后,我们可以使用生成的密钥生成解析token,以下是代码示例:
public static void main(String[] args) {
// 生成密钥,三种对称加密算法(哈希函数不同,安全性不同)
// System.out.println(Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS256).getEncoded()));
// System.out.println(Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS384).getEncoded()));
// System.out.println(Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS512).getEncoded()));
try {
// 生成对称密钥
SecretKey key = Keys.hmacShaKeyFor(KeyGeneratorUtils.generateSymmetricKey(256).getBytes(StandardCharsets.UTF_8));
String jwt = generateToken(key);
System.out.println(jwt);
boolean valid = validateToken(jwt, key);
System.out.println(valid);
if (valid) {
Claims claims = parseToken(jwt, key);
System.out.println(claims);
if (Objects.nonNull(claims)) {
System.out.println(claims.getSubject());
}
}
} catch (Exception e) {
System.err.println("Failed to generate asymmetric key: " + e.getMessage());
}
}
除了使用 KeyPairGenerator 方式生成密钥外,还可以使用上述代码中注释方法快捷生成密钥。以下是执行结果截图:
非对称加密
同样使用 KeyPairGenerator 提供的方法生成非对称密钥。以下是生成非对称密钥的代码示例:/**
* 生成非对称密钥对(RSA)
*
* @param keySize 密钥大小(如:1024, 2048)
* @return 生成的非对称密钥对的Base64编码字符串数组,第一个元素是公钥,第二个元素是私钥
* @throws NoSuchAlgorithmException 如果指定的加密算法不可用
*/
public static KeyPair generateAsymmetricKeyPair(int keySize) throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.generateKeyPair();
}
通过上述代码生成非对称密钥以后,我们可以使用生成的密钥生成解析token,以下是代码示例:
public static void main(String[] args) {
try {
// 生成非对称密钥
KeyPair keyPair = KeyGeneratorUtils.generateAsymmetricKeyPair(2048);
String jwt2 = generateToken(keyPair.getPrivate());
System.out.println(jwt2);
boolean valid2 = validateToken(jwt2, keyPair.getPublic());
System.out.println(valid2);
if (valid2) {
Claims claims = parseToken(jwt2, keyPair.getPublic());
System.out.println(claims);
if (Objects.nonNull(claims)) {
System.out.println(claims.getSubject());
}
}
} catch (Exception e) {
System.err.println("Failed to generate asymmetric key: " + e.getMessage());
}
}
生成密钥后我们使用私钥生成token,然后使用公钥校验解析token,以下是执行结果截图: