Spring Boot 微服务 - Spring Security - JUnit 的 ServiceTest 和 ControllerTest 抛出 java.lang.StackOverflowError

问题描述 投票:0回答:2

我在 Spring Boot 微服务之一(订单服务)中运行服务测试和控制器测试中的任何测试方法时遇到问题。

完成服务和控制器后,我尝试编写他们的测试方法,但在那里遇到了问题。

我该如何修复它?

这是订单服务的安全配置?

@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());
}

这是我运行如下所示的任何服务测试方法和控制器时出现的错误。

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)
    ...
    ...

要运行应用程序,

1)运行服务注册(Eureka Server)

2)运行配置服务器

3)通过 docker 上的以下命令运行 zipkin 和 redis

  docker run -d -p 9411:9411 openzipkin/zipkin
  docker run -d --name redis -p 6379:6379 redis

4)运行API网关

5)运行其他服务

这是回购协议:链接

这里是服务测试结果截图:链接

这是控制器测试的结果屏幕截图:链接

已编辑

更换

@Bean(BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
}

@Bean
public AuthenticationManager authenticationManager(List<AuthenticationProvider> myAuthenticationProviders) {
        return new ProviderManager(myAuthenticationProviders);
}

在订单服务的安全配置中。

这是下面显示的错误消息。

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
java spring-boot junit spring-security spring-cloud
2个回答
0
投票

该错误很可能是由于将 AuthenticationManager 声明为

@Bean
引起的。在你的测试类中尝试一下:

@MockBean
private AuthenticationManager _authenticationManager;

也就是说,Spring Security 团队不建议以这种方式公开 AuthenticationManager,请参阅 Spring 问题中的评论#29215


0
投票

我建议您不要依赖 Spring 自动装配列表,而是在配置中将身份验证提供程序显式定义为 beans。

喜欢:

@Bean
public AuthenticationProvider myAuthenticationProvider() {
    return new MyAuthenticationProvider();
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationProvider myAuthenticationProvider) {
    return new ProviderManager(Collections.singletonList(myAuthenticationProvider));
}
© www.soinside.com 2019 - 2024. All rights reserved.