当前位置: 首页 > article >正文

Spring Boot项目中成功集成了JWT

JWT 原理解释

什么是 JWT?

JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在网络应用环境间安全地将信息作为JSON对象传输。JWT通常用于身份验证和信息交换。

JWT 的结构

JWT由三部分组成,以点.分隔:

  1. Header(头部):包含令牌的类型(typ)和签名算法(alg)。
  2. Payload(负载):包含声明(claims),即实体(通常是用户)和其他数据。
  3. Signature(签名):通过将Base64Url编码的Header和Payload连接起来,并使用密钥进行签名得到。
生成与验证流程
  1. 生成JWT

    • 构建Header和Payload。
    • 使用指定的签名算法和密钥生成签名。
    • 将Header、Payload和Signature拼接成JWT字符串。
  2. 验证JWT

    • 解码Header和Payload。
    • 根据Header中的签名算法和提供的密钥重新计算签名。
    • 比较新计算的签名与JWT中的签名是否一致。

实现步骤

以下是详细的代码实现步骤,包括生成密钥、配置JWT、创建工具类和控制器,并加入JWT自动续期功能。

1. 准备工作

添加依赖

首先,在 pom.xml 中添加 com.auth0java-jwt 依赖:

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Auth0 JWT Library -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.10.0</version>
    </dependency>
</dependencies>

2. 生成密钥文件

对称密钥(HMAC)

我们可以使用 Java 代码生成一个对称密钥并保存到文件中:

import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;

public class KeyGenerator {
    public static void main(String[] args) throws Exception {
        // 定义一个32字节的数组用于存储密钥
        byte[] secretKey = new byte[32];
        
        // 使用强随机数生成器填充密钥数组
        SecureRandom.getInstanceStrong().nextBytes(secretKey);
        
        // 将生成的密钥写入名为 jwt_secret.key 的文件
        Files.write(Paths.get("jwt_secret.key"), secretKey);
        
        // 输出提示信息
        System.out.println("密钥已生成并保存到 jwt_secret.key 文件中");
    }
}
非对称密钥(RSA)

使用 OpenSSL 生成 RSA 密钥对,并将其保存到文件中:

# 生成私钥
openssl genpkey -algorithm RSA -out private.key -aes256 -pass pass:your_password -pkeyopt rsa_keygen_bits:2048

# 从私钥中提取公钥
openssl rsa -pubout -in private.key -out public.key -passin pass:your_password

3. 在Spring Boot中集成JWT

创建配置类

加载密钥文件并配置 JWT 相关的参数。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

@Configuration
public class JwtConfig {

    /**
     * 读取对称密钥文件
     * @return 密钥字符串
     * @throws IOException 如果读取文件失败
     */
    @Bean
    public String getSecretKey() throws IOException {
        // 从 jwt_secret.key 文件读取密钥字节数组
        byte[] keyBytes = Files.readAllBytes(Paths.get("jwt_secret.key"));
        
        // 使用Base64编码将字节数组转换为字符串
        return Base64.getEncoder().encodeToString(keyBytes);
    }

    /**
     * 读取私钥文件
     * @return 私钥对象
     * @throws Exception 如果解析私钥失败
     */
    @Bean
    public PrivateKey getPrivateKey() throws Exception {
        // 从 private.key 文件读取私钥字节数组
        byte[] keyBytes = Files.readAllBytes(Paths.get("private.key"));
        
        // 创建PKCS8EncodedKeySpec对象
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        
        // 获取RSA算法的KeyFactory实例
        KeyFactory kf = KeyFactory.getInstance("RSA");
        
        // 生成私钥对象
        return kf.generatePrivate(spec);
    }

    /**
     * 读取公钥文件
     * @return 公钥对象
     * @throws Exception 如果解析公钥失败
     */
    @Bean
    public PublicKey getPublicKey() throws Exception {
        // 从 public.key 文件读取公钥字节数组
        byte[] keyBytes = Files.readAllBytes(Paths.get("public.key"));
        
        // 创建X509EncodedKeySpec对象
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        
        // 获取RSA算法的KeyFactory实例
        KeyFactory kf = KeyFactory.getInstance("RSA");
        
        // 生成公钥对象
        return kf.generatePublic(spec);
    }
}
创建JWT工具类

实现 JWT 的生成、验证和自动续期功能。

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;

@Component
public class JwtUtil {

    @Autowired
    private String secretKey; // 对称密钥

    @Autowired
    private PrivateKey privateKey; // 私钥

    @Autowired
    private PublicKey publicKey; // 公钥

    private static final long EXPIRATION_TIME = 1000 * 60 * 60; // 1小时
    private static final long REFRESH_THRESHOLD = 1000 * 60 * 55; // 55分钟

    /**
     * 使用对称密钥生成JWT
     * @param username 用户名
     * @return 生成的JWT字符串
     */
    public String generateToken(String username) {
        // 使用对称密钥创建算法实例
        Algorithm algorithm = Algorithm.HMAC256(secretKey);
        
        // 创建JWT构建器
        return JWT.create()
                // 设置主题为用户名
                .withSubject(username)
                // 设置签发时间
                .withIssuedAt(new Date())
                // 设置过期时间为1小时后
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                // 使用指定算法签名并生成JWT字符串
                .sign(algorithm);
    }

    /**
     * 使用私钥生成JWT
     * @param username 用户名
     * @return 生成的JWT字符串
     */
    public String generateTokenWithRsa(String username) {
        // 使用RSA密钥创建算法实例
        Algorithm algorithm = Algorithm.RSA256((com.auth0.jwt.impl.crypto.RSAPublicKey) publicKey, (com.auth0.jwt.impl.crypto.RSAPrivateKey) privateKey);
        
        // 创建JWT构建器
        return JWT.create()
                // 设置主题为用户名
                .withSubject(username)
                // 设置签发时间
                .withIssuedAt(new Date())
                // 设置过期时间为1小时后
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                // 使用指定算法签名并生成JWT字符串
                .sign(algorithm);
    }

    /**
     * 验证JWT(使用对称密钥)
     * @param token 要验证的JWT字符串
     * @return 解析后的DecodedJWT对象
     */
    public DecodedJWT validateToken(String token) {
        // 使用对称密钥创建算法实例
        Algorithm algorithm = Algorithm.HMAC256(secretKey);
        
        // 创建验证器并构建验证器
        return JWT.require(algorithm)
                .build()
                // 验证并返回解码后的JWT
                .verify(token);
    }

    /**
     * 使用公钥验证JWT
     * @param token 要验证的JWT字符串
     * @return 解析后的DecodedJWT对象
     */
    public DecodedJWT validateTokenWithRsa(String token) {
        // 使用公钥创建算法实例
        Algorithm algorithm = Algorithm.RSA256((com.auth0.jwt.impl.crypto.RSAPublicKey) publicKey, null);
        
        // 创建验证器并构建验证器
        return JWT.require(algorithm)
                .build()
                // 验证并返回解码后的JWT
                .verify(token);
    }

    /**
     * 判断JWT是否需要刷新
     * @param decodedJWT 解码后的JWT
     * @return 是否需要刷新
     */
    public boolean shouldRefresh(DecodedJWT decodedJWT) {
        // 获取JWT的过期时间
        Date expiresAt = decodedJWT.getExpiresAt();
        
        // 计算当前时间和过期时间的差值
        long timeLeft = expiresAt.getTime() - System.currentTimeMillis();
        
        // 如果剩余时间小于刷新阈值,则返回true,表示需要刷新
        return timeLeft <= REFRESH_THRESHOLD;
    }

    /**
     * 刷新JWT
     * @param username 用户名
     * @return 新的JWT字符串
     */
    public String refreshToken(String username) {
        // 使用对称密钥创建算法实例
        Algorithm algorithm = Algorithm.HMAC256(secretKey);
        
        // 创建JWT构建器
        return JWT.create()
                // 设置主题为用户名
                .withSubject(username)
                // 设置签发时间
                .withIssuedAt(new Date())
                // 设置过期时间为1小时后
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                // 使用指定算法签名并生成JWT字符串
                .sign(algorithm);
    }
}
创建过滤器

在请求到达控制器之前检查JWT的有效性,并根据需要刷新JWT。

import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
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;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 从请求头中获取Authorization字段
        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Bearer ")) {
            // 如果没有Authorization字段或格式不正确,直接放行
            filterChain.doFilter(request, response);
            return;
        }

        try {
            // 提取JWT字符串
            String token = header.substring(7);

            // 验证JWT
            DecodedJWT decodedJWT = jwtUtil.validateToken(token);

            // 判断是否需要刷新JWT
            if (jwtUtil.shouldRefresh(decodedJWT)) {
                // 获取用户名
                String username = decodedJWT.getSubject();

                // 刷新JWT
                String newToken = jwtUtil.refreshToken(username);

                // 将新的JWT放入响应头
                response.setHeader("New-Token", newToken);
            }

            // 放行请求
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            // 处理异常情况,如无效的JWT
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Invalid JWT");
        }
    }
}

创建控制器

提供 API 接口来测试 JWT 的生成、验证和刷新功能。

import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class JwtController {

    @Autowired
    private JwtUtil jwtUtil;

    /**
     * 生成JWT(使用对称密钥)
     * @param username 用户名
     * @return 生成的JWT字符串
     */
    @GetMapping("/generate-token")
    public String generateToken(@RequestParam String username) {
        // 调用JwtUtil生成JWT
        return jwtUtil.generateToken(username);
    }

    /**
     * 使用私钥生成JWT
     * @param username 用户名
     * @return 生成的JWT字符串
     */
    @GetMapping("/generate-token-rsa")
    public String generateTokenWithRsa(@RequestParam String username) {
        // 调用JwtUtil使用私钥生成JWT
        return jwtUtil.generateTokenWithRsa(username);
    }

    /**
     * 验证JWT(使用对称密钥)
     * @param request 包含要验证的JWT字符串的请求体
     * @return 解析后的DecodedJWT对象
     */
    @PostMapping("/validate-token")
    public DecodedJWT validateToken(@RequestBody TokenRequest request) {
        // 调用JwtUtil验证JWT
        return jwtUtil.validateToken(request.getToken());
    }

    /**
     * 使用公钥验证JWT
     * @param request 包含要验证的JWT字符串的请求体
     * @return 解析后的DecodedJWT对象
     */
    @PostMapping("/validate-token-rsa")
    public DecodedJWT validateTokenWithRsa(@RequestBody TokenRequest request) {
        // 调用JwtUtil使用公钥验证JWT
        return jwtUtil.validateTokenWithRsa(request.getToken());
    }
}

/**
 * 请求体类,用于接收包含JWT字符串的请求
 */
class TokenRequest {
    private String token;

    public String getToken() {
        // 返回token字段值
        return token;
    }

    public void setToken(String token) {
        // 设置token字段值
        this.token = token;
    }
}

4. 完整的Spring Boot启动类

为了完整性,这里是一个简单的 Spring Boot 启动类,并注册过滤器:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class JwtDemoApplication {

    public static void main(String[] args) {
        // 运行Spring Boot应用
        SpringApplication.run(JwtDemoApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean<JwtAuthenticationFilter> loggingFilter(){
        FilterRegistrationBean<JwtAuthenticationFilter> registrationBean = new FilterRegistrationBean<>();

        registrationBean.setFilter(new JwtAuthenticationFilter());
        registrationBean.addUrlPatterns("/api/*");

        return registrationBean;
    }
}

测试API

生成JWT
  • 使用对称密钥生成JWT

    GET /api/generate-token?username=john_doe
  • 使用私钥生成JWT

    GET /api/generate-token-rsa?username=john_doe
验证JWT
  • 使用对称密钥验证JWT

    POST /api/validate-token
    Content-Type: application/json
    {
        "token": "your_jwt_token"
    }
  • 使用公钥验证JWT

    POST /api/validate-token-rsa
    Content-Type: application/json
    {
        "token": "your_jwt_token"
    }
自动续期

当JWT接近过期时(例如剩余有效期少于55分钟),过滤器会在响应头中返回一个新的JWT,客户端可以从中获取新的JWT并更新本地存储的JWT。

自动续期原理

工作流程
  1. 检查JWT有效期:每次请求到来时,过滤器会从请求头中提取JWT,并解析出其有效期。
  2. 判断是否需要续期:如果JWT的有效期剩余时间小于设定的阈值(例如55分钟),则认为该JWT即将过期,需要续期。
  3. 生成新JWT:调用 JwtUtil 类中的 refreshToken 方法生成一个新的JWT,并将其放入响应头中返回给客户端。
  4. 更新客户端JWT:客户端接收到带有新JWT的响应后,更新本地存储的JWT。
关键代码解释
判断是否需要续期
public boolean shouldRefresh(DecodedJWT decodedJWT) {
    // 获取JWT的过期时间
    Date expiresAt = decodedJWT.getExpiresAt();
    
    // 计算当前时间和过期时间的差值
    long timeLeft = expiresAt.getTime() - System.currentTimeMillis();
    
    // 如果剩余时间小于刷新阈值,则返回true,表示需要刷新
    return timeLeft <= REFRESH_THRESHOLD;
}

此方法计算JWT的剩余有效时间,如果剩余时间小于设定的阈值(如55分钟),则返回 true 表示需要刷新。

刷新JWT
public String refreshToken(String username) {
    // 使用对称密钥创建算法实例
    Algorithm algorithm = Algorithm.HMAC256(secretKey);
    
    // 创建JWT构建器
    return JWT.create()
            // 设置主题为用户名
            .withSubject(username)
            // 设置签发时间
            .withIssuedAt(new Date())
            // 设置过期时间为1小时后
            .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
            // 使用指定算法签名并生成JWT字符串
            .sign(algorithm);
}

此方法重新生成一个新的JWT,并设置其过期时间为1小时后。客户端在收到新的JWT后应更新本地存储的JWT。

过滤器处理逻辑
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    // 从请求头中获取Authorization字段
    String header = request.getHeader("Authorization");

    if (header == null || !header.startsWith("Bearer ")) {
        // 如果没有Authorization字段或格式不正确,直接放行
        filterChain.doFilter(request, response);
        return;
    }

    try {
        // 提取JWT字符串
        String token = header.substring(7);

        // 验证JWT
        DecodedJWT decodedJWT = jwtUtil.validateToken(token);

        // 判断是否需要刷新JWT
        if (jwtUtil.shouldRefresh(decodedJWT)) {
            // 获取用户名
            String username = decodedJWT.getSubject();

            // 刷新JWT
            String newToken = jwtUtil.refreshToken(username);

            // 将新的JWT放入响应头
            response.setHeader("New-Token", newToken);
        }

        // 放行请求
        filterChain.doFilter(request, response);
    } catch (Exception e) {
        // 处理异常情况,如无效的JWT
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("Invalid JWT");
    }
}

此过滤器负责在每个请求到来时检查JWT的有效性,并在需要时刷新JWT。

注意事项

  1. 密钥管理

    • 对称密钥应妥善保管,避免泄露。
    • 非对称密钥中的私钥应严格保密,公钥可以公开。
  2. 安全性

    • 设置合理的过期时间,防止长时间有效的令牌被滥用。
    • 使用HTTPS传输JWT,防止中间人攻击。
  3. 错误处理

    • 在验证JWT时捕获异常,处理无效或过期的令牌。
  4. 性能优化

    • 缓存解析后的Claims对象,减少重复解析开销。

通过上述步骤,我们已经在Spring Boot项目中成功集成了JWT,并实现了自动续期功能。


http://www.kler.cn/a/588590.html

相关文章:

  • C++学习内存管理
  • 【NLP 38、实践 ⑩ NER 命名实体识别任务 Bert 实现】
  • STM32 - 在机器人领域,LL库相比HAL优势明显
  • RAG数据嵌入和重排序:如何选择合适的模型
  • .NET_Prism基本项目创建
  • 嵌入式工程师春招面试高频题与参考回答
  • PyTorch 深度学习实战(15):Twin Delayed DDPG (TD3) 算法
  • DeepSeek在学术选题中两个核心提示词
  • Swift 中 associatedtype 的用法详解
  • 自动驾驶之心视觉语言导航!VLN算法与实战课程
  • Ubuntu从源码安装Webots
  • 【Agent】OpenManus-Agent架构详细分析
  • (vue)elementUi中el-upload上传附件之后 点击附件可下载
  • 【实操回顾】基于Apache SeaTunnel从MySQL同步到PostgreSQL——Demo方舟计划
  • C11标准对于C语言的内存模型的描述
  • 移动端自动化测试:Appium进阶技巧与常见问题排查实战指南
  • lua C语言api学习4 编写C模块
  • 颠覆大模型归一化!Meta | 提出动态Tanh:DyT,无归一化的 Transformer 性能更强
  • 【JavaEE】IOC和DI
  • Towards Universal Soccer Video Understanding——论文学习(足球类)