目录
-
- [1. JWT基础概念](#1. JWT基础概念)
-
- [1.1 什么是JWT?](#1.1 什么是JWT?)
- [1.2 JWT的应用场景](#1.2 JWT的应用场景)
- [2. JJWT 依赖配置](#2. JJWT 依赖配置)
-
- [2.1 Maven依赖](#2.1 Maven依赖)
- [2.2 Gradle依赖](#2.2 Gradle依赖)
- [2.3 依赖配置注意事项](#2.3 依赖配置注意事项)
- [3. 核心组件详解](#3. 核心组件详解)
-
- [3.1 Jwts工厂类](#3.1 Jwts工厂类)
- [3.2 主要接口与类](#3.2 主要接口与类)
- [4. JWT生成与签名](#4. JWT生成与签名)
-
- [4.1 密钥生成](#4.1 密钥生成)
- [4.2 基础JWT生成](#4.2 基础JWT生成)
- [4.3 添加自定义Claims](#4.3 添加自定义Claims)
- [4.4 使用标准Claims字段](#4.4 使用标准Claims字段)
- [5. JWT解析与验证](#5. JWT解析与验证)
-
- [5.1 基础解析](#5.1 基础解析)
- [5.2 完整验证流程](#5.2 完整验证流程)
- [6. Claims声明管理](#6. Claims声明管理)
-
- [6.1 Claims类型](#6.1 Claims类型)
- [6.2 标准Claims字段详解](#6.2 标准Claims字段详解)
- [6.3 Claims操作示例](#6.3 Claims操作示例)
- [7. 异常处理机制](#7. 异常处理机制)
-
- [7.1 JJWT常见异常类型](#7.1 JJWT常见异常类型)
- [7.2 完整异常处理示例](#7.2 完整异常处理示例)
- [8. 高级特性](#8. 高级特性)
-
- [8.1 时钟偏移容错配置](#8.1 时钟偏移容错配置)
- [8.2 刷新Token机制](#8.2 刷新Token机制)
- [8.3 安全最佳实践](#8.3 安全最佳实践)
- [9. 完整工具类示例](#9. 完整工具类示例)
-
- [9.1 JWT工具类](#9.1 JWT工具类)
- [9.2 Spring Security集成](#9.2 Spring Security集成)
- [9.3 Spring Boot配置类](#9.3 Spring Boot配置类)
- 总结
1. JWT基础概念
1.1 什么是JWT?
JSON Web Token (JWT) 是一种开放标准(RFC 7519),用于在网络应用间安全地传输信息。它由三部分组成,用点号(.)分隔:
Header.Payload.Signature
JWT结构详解:
| 组件 | 说明 | 内容示例 |
|---|---|---|
| Header | 头部信息,包含令牌类型和签名算法 | {"alg": "HS256", "typ": "JWT"} |
| Payload | 载荷信息,包含声明(Claims) | {"sub": "123456", "name": "John Doe"} |
| Signature | 签名,用于验证消息完整性和发送者身份 | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) |
1.2 JWT的应用场景
- 用户认证:用户登录后,服务器返回JWT,客户端后续请求携带该token进行身份验证
- 信息交换:安全地在不同系统间传递数据
- 单点登录(SSO):跨域认证的解决方案
- API授权:保护RESTful API接口
2. JJWT 依赖配置
2.1 Maven依赖
JJWT 0.11.5版本采用模块化设计,需要引入三个核心模块:
xml
<dependencies>
<!-- JJWT API - 编译时依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- JJWT 实现 - 运行时依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- JSON处理支持 - 运行时依赖(二选一) -->
<!-- 使用Jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- 或使用Gson -->
<!--
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-gson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
-->
</dependencies>
在jjwt 0.10+版本,原先jjwt核心包分为了api,impl两部分,Jackson为附属部分,
2.2 Gradle依赖
gradle
dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}
2.3 依赖配置注意事项
重要提示:
- 三个模块版本必须严格一致 ,否则会导致
NoSuchMethodError或ClassNotFoundException jjwt-api只包含接口定义,必须配合jjwt-impl使用- 密钥长度要求:使用HS256算法时,密钥必须≥256位(
32字节),否则会抛出WeakKeyException
3. 核心组件详解
3.1 Jwts工厂类
Jwts是JJWT库的核心工厂类,提供创建JWT相关组件的静态方法:
java
public final class Jwts {
private Jwts() {} // 私有构造器防止实例化
// 核心静态方法
public static JwtBuilder builder() { ... }
public static JwtParserBuilder parser() { ... }
public static Header header() { ... }
public static Claims claims() { ... }
}
3.2 主要接口与类
| 类/接口 | 说明 | 用途 |
|---|---|---|
Jwts |
工厂类 | 创建Builder、Parser等组件 |
JwtBuilder |
JWT构建器 | 构建和签名JWT |
JwtParserBuilder |
JWT解析器构建器 | 配置和创建解析器 |
Claims |
声明接口 | 访问JWT载荷中的声明 |
SecretKey |
密钥接口 | 签名和验证的密钥 |
SignatureAlgorithm |
签名算法枚举 | 指定签名算法 |
4. JWT生成与签名
4.1 密钥生成
HS256算法密钥生成(推荐方式)
java
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
// 方式1:使用Keys工具类生成安全密钥(推荐)
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 生成的密钥长度为256位(32字节)
// 方式2:使用自定义密钥字符串(一定要满足密钥最小字节数)
String secretString = "ThisIsAVerySecureSecretKeyWith32Bytes";
SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
// 方式3:使用Base64编码的密钥
String base64Key = "SGVsbG9Xb3JsZFNlY3VyZUtleUZvckpKV0g=";
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
SecretKey key = Keys.hmacShaKeyFor(decodedKey);
密钥长度要求
| 算法 | 最小密钥长度 | 说明 |
|---|---|---|
| HS256 | 256位(32字节) | HMAC SHA-256 |
| HS384 | 384位(48字节) | HMAC SHA-384 |
| HS512 | 512位(64字节) | HMAC SHA-512 |
4.2 基础JWT生成
java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtGenerator {
public static String generateToken(String subject, SecretKey key) {
return Jwts.builder()
.setSubject(subject) // 设置主题(通常是用户ID)
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 设置过期时间(1小时),参数单位为毫秒(ms)
.signWith(key, SignatureAlgorithm.HS256) // 使用HS256算法签名
.compact(); // 压缩生成JWT字符串
}
public static void main(String[] args) {
// 生成密钥
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 生成JWT
String jwt = generateToken("user123", key);
System.out.println("Generated JWT: " + jwt);
// 示例输出:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwiaWF0IjoxNzEzMTg5MjAwLCJleHAiOjE3MTMxOTI4MDB9.xxxxx
// 前两部分可用Base64转码工具解码查看
}
}
4.3 添加自定义Claims
java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtWithClaims {
public static String generateTokenWithClaims(String userId, String username, String role) {
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 创建自定义Claims
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
claims.put("role", role);
claims.put("department", "IT");
return Jwts.builder()
.setClaims(claims) // 设置自定义声明
.setSubject(userId) // 设置主题
.setIssuer("MyApp") // 设置签发者
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24小时
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public static void main(String[] args) {
String jwt = generateTokenWithClaims("U001", "zhangsan", "admin");
System.out.println("JWT with custom claims: " + jwt);
}
}
4.4 使用标准Claims字段
java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.UUID;
public class JwtStandardClaims {
public static String generateToken(String subject, String issuer, String[] audience) {
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
return Jwts.builder()
.setSubject(subject) // sub: 主题
.setIssuer(issuer) // iss: 签发者
.setAudience(audience) // aud: 受众
.setId(UUID.randomUUID().toString()) // jti: JWT唯一标识
.setIssuedAt(new Date()) // iat: 签发时间
.setNotBefore(new Date()) // nbf: 生效时间
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // exp: 过期时间
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public static void main(String[] args) {
String[] audience = {"web-app", "mobile-app"};
String jwt = generateToken("user123", "https://example.com", audience);
System.out.println("JWT with standard claims: " + jwt);
}
}
5. JWT解析与验证
5.1 基础解析
java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
public class JwtParser {
public static Claims parseToken(String jwt, SecretKey key) {
return Jwts.parserBuilder()
.setSigningKey(key) // 设置验证密钥
.build()
.parseClaimsJws(jwt) // 解析并验证JWT
.getBody(); // 获取Claims
}
public static void main(String[] args) {
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 生成JWT
String jwt = Jwts.builder()
.setSubject("user123")
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
// 解析JWT
Claims claims = parseToken(jwt, key);
System.out.println("Subject: " + claims.getSubject());
System.out.println("Expiration: " + claims.getExpiration());
}
}
5.2 完整验证流程
java
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtValidation {
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
/**
* 验证JWT令牌
* @param jwt JWT字符串
* @return 验证结果
*/
public static boolean validateToken(String jwt) {
try {
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(jwt);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取JWT中的用户名
* @param jwt JWT字符串
* @return 用户名
*/
public static String getUsernameFromToken(String jwt) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(jwt)
.getBody();
return claims.getSubject();
}
/**
* 检查JWT是否过期
* @param jwt JWT字符串
* @return 是否过期
*/
public static boolean isTokenExpired(String jwt) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(jwt)
.getBody();
return claims.getExpiration().before(new Date());
} catch (Exception e) {
return true;
}
}
public static void main(String[] args) {
// 生成JWT
String jwt = Jwts.builder()
.setSubject("zhangsan")
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.compact();
// 验证
System.out.println("Token valid: " + validateToken(jwt));
System.out.println("Username: " + getUsernameFromToken(jwt));
System.out.println("Expired: " + isTokenExpired(jwt));
}
}
6. Claims声明管理
6.1 Claims类型
JWT的Claims分为三类:
| 类型 | 说明 | 示例 |
|---|---|---|
| Registered Claims | 标准声明,由RFC 7519定义 | iss, sub, exp, iat等 |
| Public Claims | 公共声明,可自定义但需避免冲突 | username, role, department |
| Private Claims | 私有声明,双方约定的自定义字段 | customData, permissions |
6.2 标准Claims字段详解
| 字段 | 全称 | 说明 | 类型 |
|---|---|---|---|
| iss | Issuer | 签发者 | String |
| sub | Subject | 主题(通常是用户ID) | String |
| aud | Audience | 受众(谁能使用该令牌) | String/String[] |
| exp | Expiration Time | 过期时间 | Date/Long |
| nbf | Not Before | 在此时间之前不可用 | Date/Long |
| iat | Issued At | 签发时间 | Date/Long |
| jti | JWT ID | JWT的唯一标识符 | String |
6.3 Claims操作示例
java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class ClaimsOperations {
private static final SecretKey KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
public static void main(String[] args) {
// 1. 生成包含多种Claims的JWT
String jwt = Jwts.builder()
.setSubject("user123")
.setIssuer("MyApp")
.setAudience(new String[]{"web", "mobile"})
.setId("jwt-" + System.currentTimeMillis())
.claim("username", "zhangsan")
.claim("role", "admin")
.claim("permissions", new String[]{"read", "write", "delete"})
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(KEY, SignatureAlgorithm.HS256)
.compact();
// 2. 解析JWT获取Claims
Claims claims = Jwts.parserBuilder()
.setSigningKey(KEY)
.build()
.parseClaimsJws(jwt)
.getBody();
// 3. 访问标准Claims
System.out.println("=== 标准Claims ===");
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Audience: " + claims.getAudience());
System.out.println("JWT ID: " + claims.getId());
System.out.println("Issued At: " + claims.getIssuedAt());
System.out.println("Expiration: " + claims.getExpiration());
// 4. 访问自定义Claims
System.out.println("\n=== 自定义Claims ===");
System.out.println("Username: " + claims.get("username"));
System.out.println("Role: " + claims.get("role"));
// 5. 类型安全的访问
String username = claims.get("username", String.class);
String role = claims.get("role", String.class);
System.out.println("Type-safe username: " + username);
System.out.println("Type-safe role: " + role);
// 6. 访问数组类型的Claims
String[] permissions = claims.get("permissions", String[].class);
System.out.println("Permissions: " + String.join(", ", permissions));
// 7. 检查Claims是否存在
if (claims.containsKey("email")) {
System.out.println("Email: " + claims.get("email"));
} else {
System.out.println("Email claim not found");
}
}
}
7. 异常处理机制
7.1 JJWT常见异常类型
| 异常类 | 说明 | 触发条件 |
|---|---|---|
| SignatureException | 签名异常 | 签名验证失败,密钥不匹配 |
| ExpiredJwtException | 过期异常 | JWT已过期(exp时间已到) |
| MalformedJwtException | 格式异常 | JWT格式不正确,无法解析 |
| UnsupportedJwtException | 不支持异常 | JWT格式不受支持 |
| PrematureJwtException | 过早异常 | JWT尚未生效(nbf时间未到) |
| IllegalArgumentException | 非法参数异常 | 参数不合法,如密钥长度不足 |
7.2 完整异常处理示例
java
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import javax.crypto.SecretKey;
public class JwtExceptionHandling {
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
/**
* 安全解析JWT,处理各种异常
*/
public static Claims safeParseToken(String jwt) {
try {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(jwt)
.getBody();
} catch (ExpiredJwtException e) {
System.err.println("JWT已过期: " + e.getMessage());
throw new RuntimeException("Token已过期,请重新登录", e);
} catch (UnsupportedJwtException e) {
System.err.println("不支持的JWT格式: " + e.getMessage());
throw new RuntimeException("Token格式不支持", e);
} catch (MalformedJwtException e) {
System.err.println("JWT格式错误: " + e.getMessage());
throw new RuntimeException("Token格式错误", e);
} catch (SignatureException e) {
System.err.println("签名验证失败: " + e.getMessage());
throw new RuntimeException("Token签名无效", e);
} catch (PrematureJwtException e) {
System.err.println("JWT尚未生效: " + e.getMessage());
throw new RuntimeException("Token尚未生效", e);
} catch (IllegalArgumentException e) {
System.err.println("JWT参数错误: " + e.getMessage());
throw new RuntimeException("Token参数错误", e);
} catch (Exception e) {
System.err.println("未知错误: " + e.getMessage());
throw new RuntimeException("Token解析失败", e);
}
}
/**
* 检查Token状态并返回详细信息
*/
public static TokenValidationResult validateTokenDetailed(String jwt) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(jwt)
.getBody();
return TokenValidationResult.valid(claims);
} catch (ExpiredJwtException e) {
return TokenValidationResult.expired(e.getClaims());
} catch (SignatureException e) {
return TokenValidationResult.invalid("签名无效");
} catch (MalformedJwtException e) {
return TokenValidationResult.invalid("格式错误");
} catch (Exception e) {
return TokenValidationResult.invalid("解析失败: " + e.getMessage());
}
}
/**
* Token验证结果封装类
*/
public static class TokenValidationResult {
private final boolean valid;
private final String message;
private final Claims claims;
private TokenValidationResult(boolean valid, String message, Claims claims) {
this.valid = valid;
this.message = message;
this.claims = claims;
}
public static TokenValidationResult valid(Claims claims) {
return new TokenValidationResult(true, "Token验证通过", claims);
}
public static TokenValidationResult expired(Claims claims) {
return new TokenValidationResult(false, "Token已过期", claims);
}
public static TokenValidationResult invalid(String reason) {
return new TokenValidationResult(false, reason, null);
}
public boolean isValid() { return valid; }
public String getMessage() { return message; }
public Claims getClaims() { return claims; }
@Override
public String toString() {
return "TokenValidationResult{" +
"valid=" + valid +
", message='" + message + '\'' +
", claims=" + (claims != null ? claims.getSubject() : "null") +
'}';
}
}
public static void main(String[] args) {
// 测试1: 正常Token
String validToken = Jwts.builder()
.setSubject("user123")
.setExpiration(new java.util.Date(System.currentTimeMillis() + 3600000))
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.compact();
System.out.println("=== 测试1: 正常Token ===");
try {
Claims claims = safeParseToken(validToken);
System.out.println("Token解析成功: " + claims.getSubject());
} catch (Exception e) {
System.out.println("Token异常信息: " + e.getMessage());
}
// 测试2: 过期Token
String expiredToken = Jwts.builder()
.setSubject("user123")
.setExpiration(new java.util.Date(System.currentTimeMillis() - 1000))
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.compact();
System.out.println("\n=== 测试2: 过期Token ===");
try {
safeParseToken(expiredToken);
} catch (Exception e) {
System.out.println("Token异常信息: " + e.getMessage());
}
// 测试3: 无效签名
String invalidSignatureToken = validToken.substring(0, validToken.length() - 5) + "xxxxx";
System.out.println("\n=== 测试3: 无效签名 ===");
try {
safeParseToken(invalidSignatureToken);
} catch (Exception e) {
System.out.println("Token异常信息: " + e.getMessage());
}
// 测试4: 详细验证
System.out.println("\n=== 测试4: 详细验证 ===");
TokenValidationResult result = validateTokenDetailed(validToken);
System.out.println(result);
}
}
8. 高级特性
8.1 时钟偏移容错配置
java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class ClockSkewHandling {
private static final SecretKey KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
/**
* 设置时钟偏移容错(默认60秒)
*/
public static void withClockSkew() {
String jwt = Jwts.builder()
.setSubject("user123")
.setExpiration(new Date(System.currentTimeMillis() + 300000)) // 5分钟后过期
.signWith(KEY, SignatureAlgorithm.HS256)
.compact();
// 解析时允许60秒的时钟偏移
Claims claims = Jwts.parserBuilder()
.setSigningKey(KEY)
.setAllowedClockSkewSeconds(60) // 允许60秒时钟偏移
.build()
.parseClaimsJws(jwt)
.getBody();
System.out.println("Token parsed with clock skew tolerance");
}
public static void main(String[] args) {
withClockSkew();
}
}
8.2 刷新Token机制
java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class TokenRefreshMechanism {
private static final SecretKey ACCESS_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final SecretKey REFRESH_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
/**
* 生成访问Token(短期)
*/
public static String generateAccessToken(String userId) {
return Jwts.builder()
.setSubject(userId)
.claim("type", "access")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1800000)) // 30分钟
.signWith(ACCESS_KEY, SignatureAlgorithm.HS256)
.compact();
}
/**
* 生成刷新Token(长期)
*/
public static String generateRefreshToken(String userId) {
return Jwts.builder()
.setSubject(userId)
.claim("type", "refresh")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 604800000)) // 7天
.signWith(REFRESH_KEY, SignatureAlgorithm.HS256)
.compact();
}
/**
* 验证访问Token
*/
public static boolean validateAccessToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(ACCESS_KEY)
.build()
.parseClaimsJws(token)
.getBody();
return "access".equals(claims.get("type"));
} catch (Exception e) {
return false;
}
}
/**
* 验证刷新Token并生成新的访问Token
*/
public static String refreshAccessToken(String refreshToken) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(REFRESH_KEY)
.build()
.parseClaimsJws(refreshToken)
.getBody();
if (!"refresh".equals(claims.get("type"))) {
throw new IllegalArgumentException("Invalid refresh token");
}
// 生成新的访问Token
return generateAccessToken(claims.getSubject());
} catch (Exception e) {
throw new RuntimeException("Refresh token invalid: " + e.getMessage());
}
}
public static void main(String[] args) {
String userId = "user123";
// 生成Token对
String accessToken = generateAccessToken(userId);
String refreshToken = generateRefreshToken(userId);
System.out.println("Access Token: " + accessToken);
System.out.println("Refresh Token: " + refreshToken);
// 验证访问Token
System.out.println("\nAccess token valid: " + validateAccessToken(accessToken));
// 使用刷新Token获取新的访问Token
try {
String newAccessToken = refreshAccessToken(refreshToken);
System.out.println("New Access Token: " + newAccessToken);
} catch (Exception e) {
System.err.println("Refresh failed: " + e.getMessage());
}
}
}
8.3 安全最佳实践
java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Date;
public class SecurityBestPractices {
/**
* 最佳实践1: 使用安全随机数生成密钥
*/
public static SecretKey generateSecureKey() {
SecureRandom random = new SecureRandom();
byte[] keyBytes = new byte[32]; // 256位
random.nextBytes(keyBytes);
return Keys.hmacShaKeyFor(keyBytes);
}
/**
* 最佳实践2: 使用环境变量存储密钥
*/
public static SecretKey getKeyFromEnvironment() {
String secret = System.getenv("JWT_SECRET");
if (secret == null || secret.isEmpty()) {
throw new IllegalStateException("JWT_SECRET environment variable not set");
}
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
/**
* 最佳实践3: 设置合理的过期时间
*/
public static String generateTokenWithExpiration(String subject, long expirationMinutes) {
SecretKey key = generateSecureKey();
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationMinutes * 60000))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
/**
* 最佳实践4: 添加JWT ID防止重放攻击
*/
public static String generateTokenWithJti(String subject) {
SecretKey key = generateSecureKey();
return Jwts.builder()
.setId(java.util.UUID.randomUUID().toString()) // 唯一标识
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1800000))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
/**
* 最佳实践5: 使用Base64编码存储密钥
*/
public static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return Keys.hmacShaKeyFor(decodedKey);
}
public static void main(String[] args) {
// 示例1: 生成安全密钥
SecretKey secureKey = generateSecureKey();
System.out.println("Secure key generated");
// 示例2: 生成带过期时间的Token
String token = generateTokenWithExpiration("user123", 30);
System.out.println("Token with 30min expiration: " + token);
// 示例3: 生成带JTI的Token
String tokenWithJti = generateTokenWithJti("user123");
System.out.println("Token with JTI: " + tokenWithJti);
}
}
9. 完整工具类示例
9.1 JWT工具类
java
package com.example.security.jwt;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
/**
* JWT工具类 - 生产环境完整实现
*/
@Component
public class JwtUtil {
// 从配置文件或环境变量读取
private static final String SECRET_KEY = "MySuperSecureSecretKeyForJWTSigning2024";
private static final long ACCESS_TOKEN_VALIDITY_MS = 30 * 60 * 1000; // 30分钟
private static final long REFRESH_TOKEN_VALIDITY_MS = 7 * 24 * 60 * 60 * 1000; // 7天
private final SecretKey accessKey;
private final SecretKey refreshKey;
public JwtUtil() {
// 使用安全方式生成密钥
this.accessKey = Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
this.refreshKey = Keys.hmacShaKeyFor((SECRET_KEY + "_refresh").getBytes(StandardCharsets.UTF_8));
}
/**
* 从Token中提取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 从Token中提取JWT ID
*/
public String getJtiFromToken(String token) {
return getClaimFromToken(token, Claims::getId);
}
/**
* 从Token中提取过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 从Token中提取任意声明
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 获取Token中的所有声明
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(accessKey)
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* 检查Token是否过期
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 生成访问Token
*/
public String generateAccessToken(String username, Map<String, Object> claims) {
return doGenerateToken(username, claims, ACCESS_TOKEN_VALIDITY_MS, accessKey);
}
/**
* 生成刷新Token
*/
public String generateRefreshToken(String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("type", "refresh");
return doGenerateToken(username, claims, REFRESH_TOKEN_VALIDITY_MS, refreshKey);
}
/**
* 实际生成Token的方法
*/
private String doGenerateToken(String subject, Map<String, Object> claims,
long validityMs, SecretKey signingKey) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + validityMs))
.signWith(signingKey, SignatureAlgorithm.HS256)
.compact();
}
/**
* 验证Token
*/
public Boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(accessKey)
.build()
.parseClaimsJws(token);
return true;
} catch (SignatureException e) {
System.err.println("Invalid JWT signature: " + e.getMessage());
} catch (MalformedJwtException e) {
System.err.println("Invalid JWT token: " + e.getMessage());
} catch (ExpiredJwtException e) {
System.err.println("JWT token is expired: " + e.getMessage());
} catch (UnsupportedJwtException e) {
System.err.println("JWT token is unsupported: " + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("JWT claims string is empty: " + e.getMessage());
}
return false;
}
/**
* 验证刷新Token
*/
public Boolean validateRefreshToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(refreshKey)
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e) {
System.err.println("Refresh token validation failed: " + e.getMessage());
return false;
}
}
/**
* 从Token中获取用户信息
*/
public Map<String, Object> getUserInfoFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("username", claims.getSubject());
userInfo.put("jti", claims.getId());
userInfo.put("iat", claims.getIssuedAt());
userInfo.put("exp", claims.getExpiration());
// 提取自定义声明
claims.forEach((key, value) -> {
if (!"sub".equals(key) && !"jti".equals(key) &&
!"iat".equals(key) && !"exp".equals(key)) {
userInfo.put(key, value);
}
});
return userInfo;
}
/**
* 刷新访问Token
*/
public String refreshToken(String oldToken) {
final String username = getUsernameFromToken(oldToken);
Map<String, Object> claims = new HashMap<>();
// 保留原有自定义声明
Claims oldClaims = getAllClaimsFromToken(oldToken);
oldClaims.forEach((key, value) -> {
if (!"sub".equals(key) && !"jti".equals(key) &&
!"iat".equals(key) && !"exp".equals(key)) {
claims.put(key, value);
}
});
return generateAccessToken(username, claims);
}
// Getters
public long getAccessTokenValiditySeconds() {
return ACCESS_TOKEN_VALIDITY_MS / 1000;
}
public long getRefreshTokenValiditySeconds() {
return REFRESH_TOKEN_VALIDITY_MS / 1000;
}
}
9.2 Spring Security集成
java
package com.example.security;
import com.example.security.jwt.JwtUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* JWT认证过滤器
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// 检查Authorization头
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtUtil.getUsernameFromToken(jwtToken);
} catch (Exception e) {
logger.error("JWT解析失败: " + e.getMessage());
}
}
// 验证Token并设置认证上下文
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwtToken)) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
logger.info("认证成功: " + username);
}
}
filterChain.doFilter(request, response);
}
}
9.3 Spring Boot配置类
java
package com.example.config;
import com.example.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter,
UserDetailsService userDetailsService) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.userDetailsService = userDetailsService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // 放行认证接口
.antMatchers("/api/public/**").permitAll() // 放行公开接口
.anyRequest().authenticated() // 其他接口需要认证
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态
.and()
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
总结
全面介绍了JJWT的使用方法,涵盖了:
基础概念 :JWT结构、应用场景
依赖配置 :Maven/Gradle配置、版本管理
核心API :Jwts、Claims、SecretKey等
Token生成 :签名算法、Claims管理、过期时间设置
Token解析 :验证、异常处理、Claims提取
高级特性 :刷新机制、时钟偏移、安全最佳实践
实战示例:完整工具类、Spring Security集成