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);
}
}
✅ 说明:
/**
表示匹配所有路径- 默认允许的方法是
GET
、HEAD
、POST
,其他需显式声明 - 此配置对所有注解式控制器生效
⚠️ 组合规则:
- 多值项(如 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
包含完整测试用例,覆盖各种边界情况,建议集合备用。