升级Java21与Spring3.0后SecurityConfig循环依赖启动报错
问题
将项目升级至Java 21和Spring 3.0,同时把安全配置更新为非弃用格式后,启动失败,报错提示创建名为securityConfig的Bean时存在无法解析的循环依赖。已尝试旧的弃用配置方式及新的filterChain方式,且已将所有javax依赖替换为jakarta依赖。
报错堆栈信息
ERROR | SpringApplication - Application run failed org.springframework.context.ApplicationContextException: Unable to start web server at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:164) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:602) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1309) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1298) at io.inowise.inox.InoxApplication.main(InoxApplication.java:12) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) at org.springframework.boot.loader.Launcher.launch(Launcher.java:95) at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:65) Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:142) at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:104) at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:489) at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:211) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:183) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:161) ... 14 common frames omitted Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'securityConfig': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:412) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.resolveBeanReference(ConfigurationClassEnhancer.java:362) at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:334) at io.inowise.inox.security.SecurityConfig$$SpringCGLIB$$0.keycloakAuthenticationProvider(<generated>) at io.inowise.inox.security.SecurityConfig.configureGlobal(SecurityConfig.java:115) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:833) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:492) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1416) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:412) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204) at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:210) at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:201) at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:96) at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:85) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:261) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:235) at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:52) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4850) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:171) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1332) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1322) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:866) at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:846) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:171) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1332) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1322) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:866) at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:241) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:171) at org.apache.catalina.core.StandardService.startInternal(StandardService.java:433) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:171) at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:918) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:171) at org.apache.catalina.startup.Tomcat.start(Tomcat.java:485) at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:123) ... 19 common frames omitted
原SecurityConfig配置代码
@Configuration @EnableWebSecurity @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) public class SecurityConfig { @Value("${cors-allow-origin.url}") private String corsAllowOriginUrl; @Autowired private PermissionService permissionService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authenticationManager(authenticationManager(http)) .httpBasic().disable() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(auth -> { auth .requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll() //Other url's }); return http.build(); } private String[] getRoles(PermissionType permissionType) { return permissionService.getRoles(permissionType); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(corsAllowOriginUrl.split(","))); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } @Bean public SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new NullAuthenticatedSessionStrategy(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(keycloakAuthenticationProvider()); } @Bean public KeycloakAuthenticationProvider keycloakAuthenticationProvider() { KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider(); keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); return keycloakAuthenticationProvider; } @Bean public FilterRegistrationBean<KeycloakAuthenticationProcessingFilter> keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) { FilterRegistrationBean<KeycloakAuthenticationProcessingFilter> registrationBean = new FilterRegistrationBean<>(filter); registrationBean.setEnabled(false); return registrationBean; } @Bean public FilterRegistrationBean<KeycloakPreAuthActionsFilter> keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) { FilterRegistrationBean<KeycloakPreAuthActionsFilter> registrationBean = new FilterRegistrationBean<>(filter); registrationBean.setEnabled(false); return registrationBean; } @Bean public FilterRegistrationBean<KeycloakAuthenticatedActionsFilter> keycloakAuthenticatedActionsFilterBean(KeycloakAuthenticatedActionsFilter filter) { FilterRegistrationBean<KeycloakAuthenticatedActionsFilter> registrationBean = new FilterRegistrationBean<>(filter); registrationBean.setEnabled(false); return registrationBean; } @Bean public FilterRegistrationBean<KeycloakSecurityContextRequestFilter> keycloakSecurityContextRequestFilterBean(KeycloakSecurityContextRequestFilter filter) { FilterRegistrationBean<KeycloakSecurityContextRequestFilter> registrationBean = new FilterRegistrationBean<>(filter); registrationBean.setEnabled(false); return registrationBean; } @Bean @ConditionalOnMissingBean(HttpSessionManager.class) protected HttpSessionManager httpSessionManager() { return new HttpSessionManager(); } @Bean @ConditionalOnMissingBean(HeaderBasedConfigResolver.class) public org.keycloak.adapters.KeycloakConfigResolver keycloakConfigResolver() { return new HeaderBasedConfigResolver(); } @Bean public KeycloakRestTemplate keycloakRestTemplate(KeycloakClientRequestFactory keycloakClientRequestFactory) { return new KeycloakRestTemplate(keycloakClientRequestFactory); } @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { return http.getSharedObject(AuthenticationManagerBuilder.class).build(); } }
解决方案
问题根源
循环依赖产生的核心原因:
configureGlobal方法被@Autowired修饰,Spring在创建SecurityConfigBean的过程中会调用该方法,而方法内直接调用keycloakAuthenticationProvider()(这是一个@Bean方法),此时SecurityConfig本身还在创建中,导致Spring尝试获取的Bean依赖于正在创建的Bean,形成循环。- 手动创建
AuthenticationManager的方式不当,进一步加剧了依赖冲突。 - 旧的Keycloak过滤器注册方式与Spring Security 3.x的新配置模式不兼容。
修复步骤
- 移除
configureGlobal方法,改为在SecurityFilterChain中直接配置KeycloakAuthenticationProvider。 - 调整
AuthenticationManager的获取方式:不需要手动定义AuthenticationManagerBean,而是通过HttpSecurity的authenticationProvider方法注入认证提供者,Spring会自动管理AuthenticationManager的创建。 - 移除禁用Keycloak过滤器的
FilterRegistrationBean:在Spring Security 3.x中,Keycloak的过滤器应该通过SecurityFilterChain集成,而非注册后禁用。 - 确保Keycloak依赖版本适配Spring Boot 3.x:使用
org.keycloak:keycloak-spring-boot-starter版本21及以上,该版本已适配jakarta命名空间。
修改后的SecurityConfig代码示例
@Configuration @EnableWebSecurity @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) public class SecurityConfig { @Value("${cors-allow-origin.url}") private String corsAllowOriginUrl; @Autowired private PermissionService permissionService; @Autowired private KeycloakAuthenticationProvider keycloakAuthenticationProvider; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authenticationProvider(keycloakAuthenticationProvider) .httpBasic().disable() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(auth -> { auth .requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll() // 其他URL权限配置 }); return http.build(); } private String[] getRoles(PermissionType permissionType) { return permissionService.getRoles(permissionType); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(corsAllowOriginUrl.split(","))); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
相关产品推荐
相关产品推荐

