Spring Boot 2迁移至3后RsaSignatureValidator初始化失败排查
将Web服务从Spring Boot 2迁移到3后,DefaultSignatureValidatorFactory尝试创建RsaSignatureValidator对象时抛出NoClassDefFoundError。jar包中存在RsaSignatureValidator类,IntelliJ也能正常跳转查看代码,且相同代码在Spring Boot 2环境下运行正常。报错发生在代码进入构造函数之前,具体错误为:
java.lang.NoClassDefFoundError: Could not initialize class io.jsonwebtoken.impl.crypto.RsaSignatureValidator
首次触发静态初始化失败的堆栈跟踪如下:
java.lang.NoSuchFieldError: id_TA_ECDSA_SHA_1 at org.bouncycastle.jce.provider.asymmetric.ECMappings.<init>(Unknown Source) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) at java.base/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128) at java.base/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347) at java.base/java.lang.Class.newInstance(Class.java:645) at org.bouncycastle.jce.provider.BouncyCastleProvider.loadAlgorithms(Unknown Source) at org.bouncycastle.jce.provider.BouncyCastleProvider.setup(Unknown Source) at org.bouncycastle.jce.provider.BouncyCastleProvider.access$000(Unknown Source) at org.bouncycastle.jce.provider.BouncyCastleProvider$1.run(Unknown Source) at java.base/java.security.AccessController.doPrivileged(AccessController.java:318) at org.bouncycastle.jce.provider.BouncyCastleProvider.<init>(Unknown Source) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) at java.base/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128) at java.base/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347) at java.base/java.lang.Class.newInstance(Class.java:645) at io.jsonwebtoken.lang.Classes.newInstance(Classes.java:156) at io.jsonwebtoken.lang.RuntimeEnvironment.enableBouncyCastleIfPossible(RuntimeEnvironment.java:53) at io.jsonwebtoken.lang.RuntimeEnvironment.<clinit>(RuntimeEnvironment.java:62) at io.jsonwebtoken.impl.crypto.RsaProvider.<clinit>(RsaProvider.java:59) at io.jsonwebtoken.impl.crypto.DefaultSignatureValidatorFactory.createSignatureValidator(DefaultSignatureValidatorFactory.java:43) at io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator.<init>(DefaultJwtSignatureValidator.java:50) at io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator.<init>(DefaultJwtSignatureValidator.java:39) at io.jsonwebtoken.impl.DefaultJwtParser.createSignatureValidator(DefaultJwtParser.java:541) at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:402) at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:550) at io.jsonwebtoken.impl.ImmutableJwtParser.parse(ImmutableJwtParser.java:153) at com.okta.jwt.impl.jjwt.TokenVerifierSupport.decode(TokenVerifierSupport.java:81) at com.okta.jwt.impl.jjwt.JjwtAccessTokenVerifier.decode(JjwtAccessTokenVerifier.java:56) at io.onq.service.security.AuthTokenUtil.getClaimsFromOktaToken(AuthTokenUtil.java:108) at io.onq.service.security.AuthTokenUtil.getFromAuthProviderToken(AuthTokenUtil.java:125) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752) at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:49) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752) at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:58) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703) at io.onq.service.security.AuthTokenUtil$$SpringCGLIB$$0.getFromAuthProviderToken(<generated>) at io.onq.web.filter.UserAuthenticationTokenFilter.doFilterInternal(UserAuthenticationTokenFilter.java:61) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:351) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Thread.java:833)
从堆栈跟踪可以看出,真正的根因是java.lang.NoSuchFieldError: id_TA_ECDSA_SHA_1,这个错误发生在BouncyCastle的ECMappings类初始化阶段,进而导致RsaSignatureValidator类的静态初始化失败,最终抛出NoClassDefFoundError。按以下步骤排查:
检查BouncyCastle依赖版本冲突
执行mvn dependency:tree(Maven)或./gradlew dependencies(Gradle),输出所有org.bouncycastle相关依赖,确认是否存在多版本共存的情况(比如旧版bcprov-jdk15on和新版bcprov-jdk18on同时引入)。统一BouncyCastle依赖版本
Spring Boot 3的依赖管理默认适配较新的BouncyCastle版本,直接使用Spring Boot提供的版本即可;若项目有自定义依赖,需指定与Spring Boot 3兼容的版本(如1.77及以上),并排除冲突的旧版本依赖。验证JDK与BouncyCastle版本适配性
Spring Boot 3要求JDK 17+,BouncyCastle的不同版本对应不同JDK版本:bcprov-jdk17on适配JDK 17,bcprov-jdk18on适配JDK 18+,需确保依赖版本与当前使用的JDK匹配。排除Spring Boot自动引入的冲突依赖
如果Spring Boot starter自动引入了不兼容的BouncyCastle版本,在依赖配置中排除冲突模块。例如Maven配置:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <exclusions> <exclusion> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> </exclusion> </exclusions> </dependency>升级JJWT版本至兼容Spring Boot 3的版本
项目中使用的JJWT(io.jsonwebtoken)需升级到0.11.x及以上版本,旧版JJWT可能与Spring Boot 3、新版BouncyCastle或JDK 17+存在兼容性问题。
内容的提问来源于stack exchange,提问作者dataman

