WebTestClient - 具有 Spring Boot 和 Webflux 的 CORS

WebTestClient - CORS with Spring Boot and Webflux

我有 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

但它没有帮助,不知何故 Spring 不会在响应中发回 CORS headers,即使在控制器中使用 @CrossOrigin 注释也是如此。

我还按照 Bealdung 教程中的描述尝试使用 WebTestClient,它确认 header 不存在:

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

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

@CrossOriginJavaDocs里面提到了

Both Spring Web MVC and Spring WebFlux support this annotation through the RequestMappingHandlerMapping in their respective modules. The values from each type and method level pair of annotations are added to a CorsConfiguration

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

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

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

来自JavaDocs

Adds a CorsFilter to be used. If a bean by the name of corsFilter is provided, that CorsFilter is used. Else if corsConfigurationSource is defined, then that CorsConfiguration is used. Otherwise, if Spring MVC is on the classpath a HandlerMappingIntrospector is used.

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

@CrossOrigin(allowCredentials = "true")

解决方案是对 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>

现在,测试是绿色的!

对我来说,在不添加 spring-starter-web 依赖项的情况下实际工作的是使用网络过滤器并强制 return 如果 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

@RichArt 的解决方案已接近 99% - 但您不需要依赖项 (spring-boot-starter-web);由于它正在检查 CORS,看来 Spring 需要帮助才能知道您在测试中使用的方案和主机;它无法导出 'scheme' (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", "*");
    }

}

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("*");
      }
    };
  }
}
  • 包括来源 header 很重要。如果没有 Origin header,Access-Control-Allow-Origin header 将不会被设置,测试将失败。

  • 将 Origin 设置为与 URI 相同的主机名将被视为同一来源,因此不会设置交叉来源 header。

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

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

  • 我的答案是使用 Spring WebFlux 5.3.10.