Java Spring Boot中使用自定义证书生成JWT Token的最简实现方案咨询
嘿,我来帮你一步步搞定这个需求,尽量用最简单的方式——毕竟你说对Token、公钥这些技术不太熟,我会尽量讲明白每一步的作用~
最简实现步骤
1. 关键前提:你需要私钥
先给你划个重点:生成JWT必须用私钥签名,你现在提供的证书(.crt/.pem)里包含的是公钥,公钥是用来验证Token合法性的,不是用来生成Token的。所以你得找到当初生成这个证书时对应的私钥文件(通常是.pem格式,比如private-key.pem)——用openssl生成证书时一定会同时生成私钥,要是找不到的话可能得重新生成密钥对,但你已经有证书了,应该能找到对应的私钥~
2. 添加必要依赖(标准库无JWT实现)
Java标准库没有内置JWT的生成/解析工具,咱们用最常用的轻量库jjwt,它的API很友好,上手快。如果是Maven项目,在pom.xml里加这些依赖:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
Gradle项目的话,对应添加:
implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
3. 编写JWT生成工具类
把你的私钥文件放在src/main/resources目录下(比如命名为private-key.pem),然后写一个工具类加载私钥并生成Token:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.springframework.core.io.ClassPathResource; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.time.Instant; import java.util.Date; public class JwtTokenGenerator { // 从项目资源目录加载私钥 private static PrivateKey loadPrivateKey() throws IOException { ClassPathResource resource = new ClassPathResource("private-key.pem"); String privateKeyPem = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); // 自动解析PEM格式的私钥 return Keys.privateKeyFromPem(privateKeyPem); } // 生成JWT Token,参数可以传用户名(用来标识用户) public static String generateToken(String username) throws IOException { PrivateKey privateKey = loadPrivateKey(); // 设置Token过期时间,这里设1小时,你可以按需调整 Instant expiration = Instant.now().plusSeconds(3600); return Jwts.builder() // 设置Token的主题(一般存用户名) .setSubject(username) // 设置Token签发时间 .setIssuedAt(Date.from(Instant.now())) // 设置Token过期时间 .setExpiration(Date.from(expiration)) // 用私钥+RS256算法签名(非对称加密,比对称加密更安全) .signWith(privateKey, SignatureAlgorithm.RS256) // 生成最终的Token字符串 .compact(); } }
4. 写生成Token的Controller
创建一个Controller接口,让客户端调用它获取Token:
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @RestController public class TokenController { // 客户端调用这个接口获取Token,比如访问 GET http://localhost:8080/token?username=testUser @GetMapping("/token") public ResponseEntity<String> getToken(@RequestParam String username) { try { String token = JwtTokenGenerator.generateToken(username); return ResponseEntity.ok(token); } catch (IOException e) { return ResponseEntity.status(500).body("生成Token失败:" + e.getMessage()); } } }
5. 配置JWT认证(让其他接口能验证Token)
接下来要让你的其他接口能识别并验证这个Token,咱们用Spring Security来配置:
5.1 加载公钥(从你的证书文件)
把你的证书文件(比如certificate.crt)放在src/main/resources下,写个工具类加载公钥:
import io.jsonwebtoken.security.Keys; import org.springframework.core.io.ClassPathResource; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.PublicKey; public class JwtPublicKeyLoader { public static PublicKey loadPublicKey() throws IOException { ClassPathResource resource = new ClassPathResource("certificate.crt"); String certPem = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); // 从证书中解析出公钥 return Keys.publicKeyFromPem(certPem); } }
5.2 配置Spring Security
创建一个Security配置类,设置哪些接口需要认证,以及怎么验证Token:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import java.io.IOException; import java.security.PublicKey; @Configuration @EnableWebSecurity public class SecurityConfig { private final PublicKey publicKey; // 构造函数中加载公钥 public SecurityConfig() throws IOException { this.publicKey = JwtPublicKeyLoader.loadPublicKey(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 关闭CSRF,因为是无状态的API服务 .csrf().disable() // 不用Session,完全靠Token认证 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 设置接口权限:/token允许匿名访问,其他接口必须认证 .authorizeHttpRequests() .antMatchers("/token").permitAll() .anyRequest().authenticated() .and() // 添加JWT验证过滤器 .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } // 创建JWT验证过滤器 private JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(publicKey); } }
5.3 编写JWT验证过滤器
这个过滤器会从请求头的Authorization字段(格式是Bearer <你的Token>)里提取Token,并用公钥验证其合法性:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; 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; import java.security.PublicKey; import java.util.Collections; public class JwtAuthenticationFilter extends OncePerRequestFilter { private final PublicKey publicKey; public JwtAuthenticationFilter(PublicKey publicKey) { this.publicKey = publicKey; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头获取Authorization字段 String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } // 提取Token部分 String token = authHeader.substring(7); try { // 用公钥验证Token,并解析出里面的用户信息 Claims claims = Jwts.parserBuilder() .setSigningKey(publicKey) .build() .parseClaimsJws(token) .getBody(); // 获取用户名 String username = claims.getSubject(); // 将用户信息设置到Spring Security的上下文里,这样后续接口就能识别用户 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( username, null, Collections.emptyList() ); SecurityContextHolder.getContext().setAuthentication(authToken); } catch (Exception e) { // Token验证失败,清空认证信息 SecurityContextHolder.clearContext(); } filterChain.doFilter(request, response); } }
6. 测试一下
- 启动Spring Boot应用,调用
GET http://localhost:8080/token?username=testUser,会得到一串JWT字符串。 - 调用你其他需要认证的接口(比如
GET /api/user),在请求头里添加Authorization: Bearer <刚才拿到的Token>,就能正常访问了。
给你补点基础概念(怕你懵)
- 私钥:只有你的服务端持有,用来给JWT签名,绝对不能泄露。
- 公钥(证书):可以公开给客户端或者其他服务,用来验证Token是不是你的服务端签发的。
- RS256算法:非对称加密算法,签名用私钥,验证用公钥,比对称加密(比如HS256)更安全,不用和客户端共享密钥。
- Token过期时间:一定要设置,避免Token被盗用后永久有效,这里设了1小时,你可以根据业务需求调整。
内容的提问来源于stack exchange,提问作者stackismylifedontyouforget




