Java 21 + Spring Security 6 + JWT认证服务启动报错求助
问题描述
我用Java 21 + Spring Security 6实现基于RSA和JWT的认证服务,启动时崩溃报错,相关信息如下:
配置类代码
... import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.web.SecurityFilterChain; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; @Configuration @EnableWebSecurity public class SecurityConfig { @Value("${jwt.public.key}") private RSAPublicKey key; @Value("${jwt.private.key}") private RSAPrivateKey priv; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests( auth -> auth.requestMatchers("/authenticate").permitAll() .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()) .oauth2ResourceServer( conf -> conf.jwt(Customizer.withDefaults())); return http.build(); } @Bean JwtDecoder jwtDecoder(){ return NimbusJwtDecoder.withPublicKey(key).build(); } @Bean JwtEncoder jwtEncoder(){ var jwk = new RSAKey.Builder(key).privateKey(priv).build(); var jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwks); } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
首次报错信息
... org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityConfig': Injection of autowired dependencies failed at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:514) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1418) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:960) ~[spring-context-6.1.2.jar:6.1.2] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[spring-context-6.1.2.jar:6.1.2] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.1.jar:3.2.1] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) ~[spring-boot-3.2.1.jar:3.2.1] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:464) ~[spring-boot-3.2.1.jar:3.2.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[spring-boot-3.2.1.jar:3.2.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1358) ~[spring-boot-3.2.1.jar:3.2.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1347) ~[spring-boot-3.2.1.jar:3.2.1] at br.demo.springsecurityjwt.SpringSecurityApplication.main(SpringSecurityApplication.java:10) ~[classes/:na] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.2.1.jar:3.2.1] Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'jwt.public.key' in value "${jwt.public.key}" at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180) ~[spring-core-6.1.2.jar:6.1.2] at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-6.1.2.jar:6.1.2] at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) ~[spring-core-6.1.2.jar:6.1.2] at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) ~[spring-core-6.1.2.jar:6.1.2] at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:200) ~[spring-context-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:921) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1374) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.1.2.jar:6.1.2] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:508) ~[spring-beans-6.1.2.jar:6.1.2] ... 20 common frames omitted ...
application.properties配置
jwt.private.key=classpath:app.key jwt.public.key=classpath:app.pub
app.key和app.pub文件放在/src/main/resource目录下,密钥内容已省略。修改@Value("${jwt.public.key}")为@Value("${jwt.public.key:classpath:app.pub}")后,出现新错误:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig': Unsatisfied dependency expressed through field 'key': Failed to convert value of type 'java.lang.String' to required type 'java.security.interfaces.RSAPublicKey'; Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.security.interfaces.RSAPublicKey] for value [${jwt.public.key:classpath:app.pub]
解决方案
问题根源
- 首次错误:Spring无法直接解析
classpath:前缀的资源路径并自动转换为RSAPublicKey/RSAPrivateKey类型,@Value默认仅支持字符串注入,没有内置资源路径到密钥对象的转换逻辑。 - 二次错误:本质还是类型转换问题,字符串格式的资源路径无法直接映射为RSA密钥对象。
修复方案一:手动实现密钥读取逻辑
1. 创建RSA密钥读取工具类
import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; import java.io.BufferedReader; import java.io.InputStreamReader; 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; @Component public class RSAKeyReader { private final ResourceLoader resourceLoader; public RSAKeyReader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } public PublicKey readPublicKey(String resourcePath) throws Exception { Resource resource = resourceLoader.getResource(resourcePath); try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { StringBuilder keyBuilder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { if (!line.startsWith("-----BEGIN") && !line.startsWith("-----END")) { keyBuilder.append(line.trim()); } } byte[] decodedKey = Base64.getDecoder().decode(keyBuilder.toString()); X509EncodedKeySpec spec = new X509EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(spec); } } public PrivateKey readPrivateKey(String resourcePath) throws Exception { Resource resource = resourceLoader.getResource(resourcePath); try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { StringBuilder keyBuilder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { if (!line.startsWith("-----BEGIN") && !line.startsWith("-----END")) { keyBuilder.append(line.trim()); } } byte[] decodedKey = Base64.getDecoder().decode(keyBuilder.toString()); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(spec); } } }
2. 修改SecurityConfig配置类
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.web.SecurityFilterChain; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; @Configuration @EnableWebSecurity public class SecurityConfig { @Value("${jwt.public.key}") private String publicKeyPath; @Value("${jwt.private.key}") private String privateKeyPath; private final RSAKeyReader rsaKeyReader; public SecurityConfig(RSAKeyReader rsaKeyReader) { this.rsaKeyReader = rsaKeyReader; } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth.requestMatchers("/authenticate").permitAll() .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()) .oauth2ResourceServer(conf -> conf.jwt(Customizer.withDefaults())); return http.build(); } @Bean JwtDecoder jwtDecoder() throws Exception { RSAPublicKey publicKey = (RSAPublicKey) rsaKeyReader.readPublicKey(publicKeyPath); return NimbusJwtDecoder.withPublicKey(publicKey).build(); } @Bean JwtEncoder jwtEncoder() throws Exception { RSAPublicKey publicKey = (RSAPublicKey) rsaKeyReader.readPublicKey(publicKeyPath); RSAPrivateKey privateKey = (RSAPrivateKey) rsaKeyReader.readPrivateKey(privateKeyPath); var jwk = new RSAKey.Builder(publicKey).privateKey(privateKey).build(); var jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwks); } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
修复方案二:使用Spring Boot自动配置(更简洁)
添加spring-security-oauth2-resource-server-jwt依赖后,直接修改配置文件让Spring自动处理密钥加载:
1. 修改application.properties
spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:app.pub spring.security.oauth2.resourceserver.jwt.private-key-location=classpath:app.key
2. 简化SecurityConfig
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth.requestMatchers("/authenticate").permitAll() .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()) .oauth2ResourceServer(conf -> conf.jwt(Customizer.withDefaults())); return http.build(); } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
内容的提问来源于stack exchange,提问作者Dev
相关产品推荐
相关产品推荐

