1. 概述

之前的文章中,我们已经了解了跨域资源共享(CORS)的基本原理以及在 Spring 中的常规使用方式。

本文将聚焦于 Spring 5 的响应式框架 WebFlux,演示如何在非阻塞、函数式编程模型下配置 CORS。虽然底层机制类似,但 WebFlux 的响应式特性带来了一些细微差别,稍不注意就容易踩坑。

我们将覆盖以下三种主流方式: ✅ 基于注解的局部配置
✅ 全局配置(适用于注解式控制器)
✅ 使用 WebFilter 的函数式端点支持


2. 在注解式接口上启用 CORS

Spring 提供了 @CrossOrigin 注解,可以方便地为控制器类或具体处理方法开启 CORS 支持。

2.1 在请求处理方法上使用 @CrossOrigin

最简单的用法是直接标注在 @RequestMapping 系列注解的方法上:

@CrossOrigin
@PutMapping("/cors-enabled-endpoint")
public Mono<String> corsEnabledEndpoint() {
    return Mono.just("CORS 已启用");
}

我们可以通过 WebTestClient 验证响应头是否正确返回:

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

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

再通过一个预检请求(preflight)验证 OPTIONS 响应是否符合预期:

ResponseSpec response = webTestClient.options()
  .uri("/cors-enabled-endpoint")
  .header("Origin", "http://any-origin.com")
  .header("Access-Control-Request-Method", "PUT")
  .exchange();

response.expectHeader()
  .valueEquals("Access-Control-Allow-Origin", "*");
response.expectHeader()
  .valueEquals("Access-Control-Allow-Methods", "PUT");
response.expectHeader()
  .exists("Access-Control-Max-Age");

⚠️ @CrossOrigin 的默认行为如下:

  • 允许所有来源(即 Access-Control-Allow-Origin: *
  • 允许所有请求头
  • 自动允许该方法支持的 HTTP 动作(如 @PutMapping 则允许 PUT)
  • 不支持凭据(credentials)
  • max-age 缓存时间为 1800 秒(30 分钟)

这些都可以通过注解参数覆盖,比如指定允许的 origin:

@CrossOrigin(origins = "http://trusted-site.com")
@PutMapping("/secure-cors")
public Mono<String> secureEndpoint() {
    return Mono.just("仅限 trusted-site.com 访问");
}

2.2 在控制器类上使用 @CrossOrigin

你也可以将 @CrossOrigin 标注在控制器类上,作用于其所有处理方法:

@CrossOrigin(
    value = { "http://allowed-origin.com" },
    allowedHeaders = { "Baeldung-Allowed" },
    maxAge = 900
)
@RestController
public class CorsOnClassController {

    @PutMapping("/cors-enabled-endpoint")
    public Mono<String> corsEnabledEndpoint() {
        return Mono.just("继承类级 CORS 配置");
    }

    @CrossOrigin({ "http://another-allowed-origin.com" })
    @PutMapping("/endpoint-with-extra-origin-allowed")
    public Mono<String> corsEnabledWithExtraAllowedOrigin() {
        return Mono.just("此接口额外允许 another-allowed-origin.com");
    }
}

✅ 注意优先级规则:

  • 多值属性(如 origins、headers)是合并生效
  • 单值属性(如 maxAge)以局部注解优先

⚠️ 这种方式不适用于函数式端点(functional endpoints),因为没有方法或类可供注解。


3. 全局 CORS 配置

如果你希望统一管理整个项目的跨域策略,可以通过实现 WebFluxConfigurer 接口并重写 addCorsMappings() 方法来完成。

@Configuration
@EnableWebFlux
public class CorsGlobalConfiguration implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {
        corsRegistry.addMapping("/**")
          .allowedOrigins("http://allowed-origin.com")
          .allowedMethods("PUT")
          .maxAge(3600);
    }
}

✅ 说明:

  • /** 表示匹配所有路径
  • 默认允许的方法是 GETHEADPOST,其他需显式声明
  • 此配置对所有注解式控制器生效

⚠️ 组合规则:

  • 多值项(如 origins、methods)是叠加
  • 单值项(如 maxAge)局部 > 全局

例如,若全局设置 maxAge=3600,而某个方法用 @CrossOrigin(maxAge=1800),最终该接口生效的是 1800 秒。

❌ 局限性:

  • 无法用于函数式端点
  • 灵活性不如 WebFilter 方式

4. 使用 WebFilter 启用 CORS

对于基于 RouterFunction 的函数式编程模型,**最推荐的方式是使用 WebFilter**。它不依赖注解,适用于所有类型的端点。

Spring 提供了开箱即用的 CorsWebFilter,配置非常简单:

@Bean
CorsWebFilter corsWebFilter() {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowedOrigins(Arrays.asList("http://allowed-origin.com"));
    corsConfig.setMaxAge(8000L);
    corsConfig.addAllowedMethod("PUT");
    corsConfig.addAllowedHeader("Baeldung-Allowed");

    UrlBasedCorsConfigurationSource source =
      new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);

    return new CorsWebFilter(source);
}

✅ 优势:

  • 适用于注解式和函数式两种编程模型
  • 配置集中,便于统一管理
  • 与安全链(Security Chain)天然兼容

⚠️ 注意事项:

  • CorsConfiguration 没有默认值,必须显式设置,否则会非常严格(相当于拒绝所有)
  • 推荐调用 applyPermitDefaultValues() 快速设置宽松策略:
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.applyPermitDefaultValues(); // 自动允许 GET/HEAD/POST,通配 origin 等
corsConfig.setAllowedOrigins(Arrays.asList("http://custom-origin.com"));

❌ 限制:

  • 一旦使用 CorsWebFilter不能再与 @CrossOrigin 混用,否则可能导致冲突或重复头信息

5. 总结

方式 适用场景 是否支持函数式 是否可与其它方式组合
@CrossOrigin 注解式控制器,局部控制 ✅(与全局配置)
WebFluxConfigurer 注解式项目,全局统一 ✅(叠加)
CorsWebFilter 所有场景,尤其是函数式端点 ❌(避免混用)

📌 选择建议:

  • 普通 REST API 项目 ➜ 用 @CrossOrigin 或全局配置,简单粗暴
  • 响应式微服务 + 函数式路由 ➜ 必须用 CorsWebFilter
  • 混合模式项目 ➜ 统一使用 CorsWebFilter 避免混乱

所有示例代码均已上传至 GitHub: 👉 https://github.com/baeldung/spring-reactive-tutorial
包含完整测试用例,覆盖各种边界情况,建议集合备用。


原始标题:Spring Webflux and CORS | Baeldung