Spring Boot微服务测试遇StackOverflowError及AuthenticationManager配置异常
Spring Boot微服务订单服务JUnit测试StackOverflowError及AuthenticationManager配置问题解决
问题描述
在Spring Boot微服务的订单服务中编写Service和Controller的JUnit测试时,运行任意测试方法均抛出java.lang.StackOverflowError。完成Service与Controller开发后编写测试代码就出现该问题。尝试修改SecurityConfig中的AuthenticationManager配置后,又触发Bean创建失败异常,提示‘需要父AuthenticationManager或AuthenticationProviders列表’。
相关代码
SecurityConfig配置
@Configuration @EnableWebSecurity(debug = true) @EnableGlobalMethodSecurity(prePostEnabled = true) @RequiredArgsConstructor public class SecurityConfig { private final JwtAuthenticationEntryPoint authenticationEntryPoint; private final JWTAccessDeniedHandler accessDeniedHandler; private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean(BeanIds.AUTHENTICATION_MANAGER) public AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf().disable() .authorizeRequests() .antMatchers(HttpMethod.POST, "/order/**").hasRole("USER") .antMatchers(HttpMethod.GET, "/order/**").hasRole("USER") .and() .authorizeRequests().anyRequest().authenticated() .and() .formLogin().disable() .httpBasic().disable() .exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .build(); } @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().antMatchers("/authenticate/signup", "/authenticate/login", "/authenticate/refreshtoken"); } }
OrderServiceTest测试方法示例
@DisplayName("Get Order - Success Scenario") @Test void test_When_Order_Success() { //Mocking Order order = getMockOrder(); when(orderRepository.findById(anyLong())) .thenReturn(Optional.of(order)); when(restTemplate.getForObject( "http://PRODUCT-SERVICE/product/" + order.getProductId(), ProductResponse.class )).thenReturn(getMockProductResponse()); when(restTemplate.getForObject( "http://PAYMENT-SERVICE/payment/order/" + order.getId(), PaymentResponse.class )).thenReturn(getMockPaymentResponse()); //Actual OrderResponse orderResponse = orderService.getOrderDetails(1); //Verification verify(orderRepository, times(1)).findById(anyLong()); verify(restTemplate, times(1)).getForObject( "http://PRODUCT-SERVICE/product/" + order.getProductId(), ProductResponse.class); verify(restTemplate, times(1)).getForObject( "http://PAYMENT-SERVICE/payment/order/" + order.getId(), PaymentResponse.class); //Assert assertNotNull(orderResponse); assertEquals(order.getId(), orderResponse.getOrderId()); }
OrderControllerTest测试方法示例
@Test @DisplayName("Place Order -- Success Scenario") @WithMockUser(username = "User", authorities = { "ROLE_USER" }) void test_When_placeOrder_DoPayment_Success() throws Exception { OrderRequest orderRequest = getMockOrderRequest(); String jwt = getJWTTokenForRoleUser(); MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/order/placeorder") .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + jwt) .content(objectMapper.writeValueAsString(orderRequest))) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); String orderId = mvcResult.getResponse().getContentAsString(); Optional<Order> order = orderRepository.findById(Long.valueOf(orderId)); assertTrue(order.isPresent()); Order o = order.get(); assertEquals(Long.parseLong(orderId), o.getId()); assertEquals("PLACED", o.getOrderStatus()); assertEquals(orderRequest.getTotalAmount(), o.getAmount()); assertEquals(orderRequest.getQuantity(), o.getQuantity()); }
报错信息
初始StackOverflowError
java.lang.StackOverflowError at java.base/java.lang.Throwable.getOurStackTrace(Throwable.java:861) at java.base/java.lang.Throwable.getStackTrace(Throwable.java:853) at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:79) ... ...
修改AuthenticationManager后的Bean创建失败异常
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authenticationManager' defined in class path resource [com/microservice/orderservice/config/SecurityConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.authentication.AuthenticationManager]: Factory method 'authenticationManager' threw exception; nested exception is java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.22.jar:5.3.22] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.22.jar:5.3.22] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.3.jar:2.7.3] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-2.7.3.jar:2.7.3] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.3.jar:2.7.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-2.7.3.jar:2.7.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.7.3.jar:2.7.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-2.7.3.jar:2.7.3] at com.microservice.orderservice.OrderServiceApplication.main(OrderServiceApplication.java:15) ~[classes/:na] Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.authentication.AuthenticationManager]: Factory method 'authenticationManager' threw exception; nested exception is java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.22.jar:5.3.22] ... 19 common frames omitted Caused by: java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required at org.springframework.util.Assert.isTrue(Assert.java:121) ~[spring-core-5.3.22.jar:5.3.22] at org.springframework.security.authentication.ProviderManager.checkState(ProviderManager.java:138) ~[spring-security-core-5.7.3.jar:5.7.3] at org.springframework.security.authentication.ProviderManager.<init>(ProviderManager.java:129) ~[spring-security-core-5.7.3.jar:5.7.3] at org.springframework.security.authentication.ProviderManager.<init>(ProviderManager.java:117) ~[spring-security-core-5.7.3.jar:5.7.3] at com.microservice.orderservice.config.SecurityConfig.authenticationManager(SecurityConfig.java:42) ~[classes/:na] at com.microservice.orderservice.config.SecurityConfig$$EnhancerBySpringCGLIB$$83744c5.CGLIB$authenticationManager$0(<generated>) ~[classes/:na] at com.microservice.orderservice.config.SecurityConfig$$EnhancerBySpringCGLIB$$83744c5$$FastClassBySpringCGLIB$$8d59030c.invoke(<generated>) ~[classes/:na] at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.3.22.jar:5.3.22] at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.22.jar:5.3.22] at com.microservice.orderservice.config.SecurityConfig$$EnhancerBySpringCGLIB$$83744c5.authenticationManager(<generated>) ~[classes/:na] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:578) ~[na:na] at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.22.jar:5.3.22] ... 20 common frames omitted
应用启动步骤
- 运行服务注册中心(Eureka Server)
- 运行配置中心
- 通过Docker命令启动zipkin和redis:
docker run -d -p 9411:9411 openzipkin/zipkin docker run -d --name redis -p 6379:6379 redis - 运行API网关
- 运行其他服务
解决方案
1. 解决StackOverflowError问题
该错误通常由循环依赖或测试上下文加载时的无限递归导致,针对测试场景可按以下方式处理:
- Service层测试优化:使用
@MockBean替代手动Mock,避免加载不必要的Spring上下文。示例:@SpringBootTest class OrderServiceTest { @Autowired private OrderService orderService; @MockBean private OrderRepository orderRepository; @MockBean private RestTemplate restTemplate; // 测试方法实现 } - Controller层测试优化:使用
@WebMvcTest专注于Controller测试,仅加载必要的Web上下文:@WebMvcTest(OrderController.class) class OrderControllerTest { @Autowired private MockMvc mockMvc; @MockBean private OrderService orderService; // 测试方法实现 } - 检查
JwtAuthenticationFilter是否存在循环依赖(例如Filter注入AuthenticationManager,而AuthenticationManager又依赖Filter相关Bean),导致上下文加载时递归调用。
2. 解决AuthenticationManager配置问题
原配置通过AuthenticationConfiguration.getAuthenticationManager()获取AuthenticationManager的方式是正确的,修改后的配置因未提供AuthenticationProviders或父AuthenticationManager导致报错,直接恢复原配置即可:
@Bean(BeanIds.AUTHENTICATION_MANAGER) public AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }
若确实需要自定义AuthenticationManager,需至少配置一个AuthenticationProvider,示例:
@Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); provider.setPasswordEncoder(passwordEncoder); return provider; } @Bean public AuthenticationManager authenticationManager(List<AuthenticationProvider> authenticationProviders) { return new ProviderManager(authenticationProviders); }
但在JWT认证场景下,原配置方式更适配,因为AuthenticationConfiguration会自动处理默认的AuthenticationManager配置。
3. 测试时跳过Security验证(可选)
若测试无需Security校验,可通过以下方式实现:
- 在测试类添加
@AutoConfigureMockMvc(addFilters = false) - 创建测试专用SecurityConfig并激活测试Profile:
测试类添加@Profile("test") @Configuration @EnableWebSecurity public class TestSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.csrf().disable() .authorizeRequests().anyRequest().permitAll() .build(); } }@ActiveProfiles("test")
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

