不清楚JWT令牌的特点与优势,看这篇blog

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版本采用模块化设计,需要引入三个核心模块:

<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依赖

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 依赖配置注意事项

重要提示

  • 三个模块版本必须严格一致,否则会导致NoSuchMethodErrorClassNotFoundException
  • jjwt-api只包含接口定义,必须配合jjwt-impl使用
  • 密钥长度要求:使用HS256算法时,密钥必须≥256位(32字节),否则会抛出WeakKeyException

3. 核心组件详解

3.1 Jwts工厂类

Jwts是JJWT库的核心工厂类,提供创建JWT相关组件的静态方法:

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等组件
JwtBuilderJWT构建器构建和签名JWT
JwtParserBuilderJWT解析器构建器配置和创建解析器
Claims声明接口访问JWT载荷中的声明
SecretKey密钥接口签名和验证的密钥
SignatureAlgorithm签名算法枚举指定签名算法

4. JWT生成与签名

4.1 密钥生成

HS256算法密钥生成(推荐方式)
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);

Base64转码工具

密钥长度要求
算法最小密钥长度说明
HS256256位(32字节)HMAC SHA-256
HS384384位(48字节)HMAC SHA-384
HS512512位(64字节)HMAC SHA-512

4.2 基础JWT生成

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

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字段

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 基础解析

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 完整验证流程

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字段详解

字段全称说明类型
issIssuer签发者String
subSubject主题(通常是用户ID)String
audAudience受众(谁能使用该令牌)String/String[]
expExpiration Time过期时间Date/Long
nbfNot Before在此时间之前不可用Date/Long
iatIssued At签发时间Date/Long
jtiJWT IDJWT的唯一标识符String

6.3 Claims操作示例

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 完整异常处理示例

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 时钟偏移容错配置

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机制

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 安全最佳实践

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工具类

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集成

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配置类

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集成

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐