You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 11:01:08