Java-JWT令牌技术深度指南

目录

    • [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生成与签名)
    • [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配置类)
    • 总结

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

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

重要提示

  • 三个模块版本必须严格一致 ,否则会导致NoSuchMethodErrorClassNotFoundException
  • 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);

Base64转码工具

密钥长度要求
算法 最小密钥长度 说明
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集成

相关推荐
木井巳1 天前
【递归算法】组合总和
java·算法·leetcode·决策树·深度优先·剪枝
量子炒饭大师1 天前
【C++ 进阶】Cyber霓虹掩体下的代码拟态——【面向对象编程 之 多态】一文带你搞懂C++面向对象编程中的三要素之一————多态!
开发语言·c++·多态
消失的旧时光-19431 天前
Spring Boot 入门实战(二):用户注册接口设计(Controller + DTO + Validation)
java·spring boot·接口
xiaoshuaishuai81 天前
C# 实现百度搜索算法逆向
开发语言·windows·c#·dubbo
A-Jie-Y1 天前
JAVA框架-SpringBoot环境搭建指南
java·spring boot
yuan199971 天前
使用模糊逻辑算法进行路径规划(MATLAB实现)
开发语言·算法·matlab
深兰科技1 天前
深兰科技与淡水河谷合作推进:矿区示范加速落地
java·人工智能·python·c#·scala·symfony·深兰科技
码界奇点1 天前
基于Spring Boot的前后端分离商城系统设计与实现
java·spring boot·后端·java-ee·毕业设计·源代码管理
一叶飘零_sweeeet1 天前
深度剖析:Java 并发三大量难题 —— 死锁、活锁、饥饿全解
java·死锁·活锁·饥饿
蒸汽求职1 天前
北美求职身份过渡:Day 1 CPT 的合规红线与安全入职指南
开发语言·人工智能·安全·pdf·github·开源协议