WebTestClient - 带有 Spring Boot 和 Webflux 的 CORS

Posted

技术标签:

【中文标题】WebTestClient - 带有 Spring Boot 和 Webflux 的 CORS【英文标题】:WebTestClient - CORS with Spring Boot and Webflux 【发布时间】:2020-10-24 15:20:50 【问题描述】:

我有 Vuejs 前端和一个 Spring Boot Webflux 控制器。现在浏览器在调用 Spring Boot 时会抱怨 CORS。

Access to XMLHttpRequest at 'https://...' from origin 'https://...' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

为了启用 CORS,我尝试使用@CrossOrigin 注释,如下所述:https://www.baeldung.com/spring-webflux-cors

但这并没有帮助,并且即使在控制器中使用 @CrossOrigin 注释,Spring 也不会在响应中将 CORS 标头发送回。

我还尝试了 Bealdung 教程中描述的 WebTestClient,它确认标题不存在:

java.lang.AssertionError: Response header 'Access-Control-Allow-Origin' expected:<[*]> but was:<null>

> OPTIONS /cors-enabled-endpoint
> WebTestClient-Request-Id: [1]
> Origin: [http://any-origin.com]
> Access-Control-Request-Method: [GET]

No content

< 500 INTERNAL_SERVER_ERROR Internal Server Error
< Vary: [Origin, Access-Control-Request-Method, Access-Control-Request-Headers]

0 bytes of content (unknown content-type).

如果我用response.expectHeader().exists("Access-Control-Allow-Origin"); 测试 我明白了:

java.lang.AssertionError: Response header 'Access-Control-Allow-Origin' does not exist

知道为什么上面链接中描述的 CORS 配置不起作用吗?我还尝试在全局配置上启用 CORS 并使用 WebFilter 启用 CORS。但似乎没有任何效果。

【问题讨论】:

【参考方案1】:

@CrossOrigin 的JavaDocs 中有提及

Spring Web MVC 和 Spring WebFlux 都通过 RequestMappingHandlerMapping 在各自的模块中。这 添加来自每个类型和方法级别注释对的值 到 CorsConfiguration

这意味着您的 CORS 设置将添加到 CorsConfiguration

如果您使用 Spring Security,您需要 enable CORS 以便使用您的 CorsConfiguration。看下面一个小例子

   protected void configure(HttpSecurity http) throws Exception 
        http
            .cors()
            ...
    

来自JavaDocs

添加要使用的 CorsFilter。如果名为 corsFilter 的 bean 是 提供,使用 CorsFilter。否则,如果 corsConfigurationSource 是 定义,然后使用 CorsConfiguration。否则,如果 Spring MVC 在使用 HandlerMappingIntrospector 的类路径上。

注意,如果您通过请求 (cookie) 发送凭据,则需要将其添加到您的 CORS 设置中

@CrossOrigin(allowCredentials = "true")

【讨论】:

非常感谢您的回答。仔细阅读并再次阅读文档后,我仍然遇到同样的问题。实际上,我不使用 Spring Security。还有一件事:运行测试也给出了这个错误:2020-07-04 13:44:45.611 ERROR 17867 --- [ parallel-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [38a66881] 500 Server Error for HTTP GET "/cors-enabled-endpoint"java.lang.IllegalArgumentException: Actual request scheme must not be null这可能是根本原因,但我在互联网上找不到帮助资源。你能帮忙吗? @RichArt 确保将 CrossOrigin 放在与您发送请求的完全相同的请求映射上。如果您将 CrossOrigin 放在 GET "/api/users" 上,那么 CORS 将仅对 GET "/api/users" 启用。您也可以将 CrossOrigin 放在控制器级别,然后 CORS 将可用于控制器中的所有请求映射,例如POST "/api/users" GET "/api/users" 等等,当然,这取决于你在控制器的 CORS 配置中允许的方法。【参考方案2】:

解决方案是为 WebTestClient 使用以下配置:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient

class MyTestClass 

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void corsTest() 
        ResponseSpec response = webTestClient.get()
                .uri("/cors-enabled-endpoint")
                .header("Origin", "http://any-origin.com")
                .exchange();

        response.expectHeader()
                .valueEquals("Access-Control-Allow-Origin", "*");
    


而且我还需要添加spring-boot-starter-web 依赖,这很奇怪,因为我只使用Webflux。但是没有它,测试仍然会失败,IllegalArgumentException: Actual request scheme must not be null

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
</dependency>

现在,测试是绿色的!

【讨论】:

【参考方案3】:

对我来说,不添加 spring-starter-web 依赖项的实际工作是使用 web 过滤器并在 http 方法为 OPTIONS 时强制返回。

@Configuration
public class CorsGlobalConfiguration implements WebFilter 


@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
                         WebFilterChain webFilterChain) 
    ServerHttpRequest request = serverWebExchange.getRequest();
    ServerHttpResponse response = serverWebExchange.getResponse();
    HttpHeaders headers = response.getHeaders();
    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
    headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
    headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000L");
    if (request.getMethod() == HttpMethod.OPTIONS) 
        response.setStatusCode(HttpStatus.OK);
        return Mono.empty();//HERE
    
    return webFilterChain.filter(serverWebExchange);




Source

【讨论】:

【参考方案4】:

@RichArt 的解决方案 99% 接近 - 但您不需要依赖项 (spring-boot-starter-web);由于它正在检查 CORS,因此 Spring 似乎需要帮助了解您在测试中使用的方案和主机;它无法派生“方案”(http)或具有较短 URI 的主机。

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class MyTestClass 

    @Autowired
    private WebTestClient webTestClient;

    @LocalServerPort
    private Int lsp;

    @Test
    void corsTest() 
        ResponseSpec response = webTestClient.get()
                .uri("http://localhost" + lsp.toString() + "/cors-enabled-endpoint")
                .header("Origin", "http://any-origin.com")
                .exchange();

        response.expectHeader()
                .valueEquals("Access-Control-Allow-Origin", "*");
    


【讨论】:

非常感谢。是的,添加 mvc 的东西可能不是一个好主意。【参考方案5】:

Thinice 的解决方案是 99.9%。 Spring WebTestClient CORS 测试要求 URI 包含 any 主机名和端口。 实际主机名和端口被 WebTestClient 忽略以连接到测试 Web 服务器:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class MyTestClass 

    @Autowired
    private WebTestClient webTestClient;
    
    @Test
    void corsTest() 
        webTestClient.get()
                .uri("http://hostname-ignored:666/cors-enabled-endpoint")
                .header("Origin", "http://any-origin.com")
                .exchange()   
                .expectHeader()
                  .valueEquals("Access-Control-Allow-Origin", "*");
        

此外,可以省略显式端口,它将继续工作:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class MyTestClass 

    @Autowired
    private WebTestClient webTestClient;
    
    @Test
    void corsTest() 
        webTestClient.get()
                .uri("http://look-ma-no-port/cors-enabled-endpoint")
                .header("Origin", "http://any-origin.com")
                .exchange()   
                .expectHeader()
                  .valueEquals("Access-Control-Allow-Origin", "*");
    

要为所有端点启用 CORS,可以使用给定的 WebFluxConfigurer:

@Configuration(proxyBeanMethods = false)
public class MyWebFluxConfigurer 

  @Bean
  public WebFluxConfigurer corsConfigurer() 
    return new WebFluxConfigurerComposite() 

      @Override
      public void addCorsMappings(CorsRegistry registry) 
        registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
      
    ;
  

包含 Origin 标头很重要。如果没有 Origin 标头,则不会设置 Access-Control-Allow-Origin 标头,并且测试将失败。

将 Origin 设置为与 URI 相同的主机名将被视为同源,因此不会设置跨域标头。

RichArt 建议包含 spring-boot-starter-web 依赖项并不是启用 CORS 测试的具体要求。

此答案仅适用于测试。它假定您已在服务器中启用 CORS。

我的答案是使用 Spring WebFlux 5.3.10。

【讨论】:

以上是关于WebTestClient - 带有 Spring Boot 和 Webflux 的 CORS的主要内容,如果未能解决你的问题,请参考以下文章

spring boot:如何配置自动装配的 WebTestClient

Spring boot - WebFlux - WebTestClient - 将响应转换为 responseEntity

springboot~WebTestClient的使用

如何让 WebTestClient 注销所有请求 [重复]

SpringBoot2 + Webflux - WebTestClient 总是返回“401 UNAUTHORIZED”

SpringBoot WebTestClient - 如何在 graphql API 中使用 expectBody