公司有 30 多个微服务,每个服务的后端开发都自己配了一份 CORS。结果前端调接口时,有的服务返回了
Access-Control-Allow-Origin,有的没返回,有的返回了*,有的返回了具体域名。浏览器一看响应头不一致——有的 OPTIONS 预检过了,有的直接被 CORS 策略拦住,前端报了满屏的blocked by CORS policy。查了半天才发现是服务 A 配了allowedOrigins(*, example.com),服务 B 配了allowedOrigins(example.com),服务 C 根本没配。
CORS 应该是网关的活,不是每个微服务的活。所有跨域请求都先到网关,网关统一返回 CORS 响应头,后端服务根本不应该感知到 CORS 的存在。
为什么 CORS 不应该散落在微服务里
CORS 本质是浏览器的一个安全机制——它确保一个域名的脚本不能随意访问另一个域名的资源。网关是所有请求的入口,天然适合做 CORS 的"守门员"。
散落在各个微服务的问题:
- 配置不一致 —— 30 个服务 30 份 CORS 配置,改一个域名要改 30 个地方
- OPTIONS 预检穿透 —— OPTIONS 请求是浏览器自动发的,目标服务如果没配 CORS,直接 403
- 重复的响应头 —— 服务 A 加了
Access-Control-Allow-Origin,网关再加一次,浏览器看到两个可能拒绝
解决思路很简单:微服务全部关闭 CORS,只在网关层配一份。
网关统一 CORS 配置
Spring Cloud Gateway 有 CorsWebFilter,配起来就几行:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins:
- "https://admin.example.com"
- "https://app.example.com"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders:
- "Authorization"
- "Content-Type"
- "X-Request-ID"
allowCredentials: true
maxAge: 3600
每个请求进来,网关自动根据上面的配置生成 CORS 响应头。前端不管调的是订单服务还是支付服务,CORS 响应头都是网关统一返回的——浏览器看到的一致,不会因为"有时候有有时候没有"而报错。
动态响应头:不是所有接口都不该被跨域
网关统一 CORS 能解决大部分问题,但有些场景需要更精细的控制。比如公开 API 允许所有域名跨域,管理后台 API 只允许 admin.example.com 跨域。
这时需要一个动态 CORS 拦截器——根据请求路径匹配不同的跨域策略:
@Component
public class DynamicCorsFilter implements GlobalFilter {
// 路径 → 允许的源
private static final Map<Pattern, List<String>> RULES = Map.of(
Pattern.compile("/api/public/.*"), List.of("*"),
Pattern.compile("/api/admin/.*"), List.of("https://admin.example.com"),
Pattern.compile("/api/**"), List.of("https://app.example.com")
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
String path = exchange.getRequest().getPath().value();
// 匹配路径 → 设置对应的 CORS 头
for (var entry : RULES.entrySet()) {
if (entry.getKey().matcher(path).matches()) {
String origins = String.join(",", entry.getValue());
response.getHeaders().add("Access-Control-Allow-Origin", origins);
response.getHeaders().add("Access-Control-Allow-Methods",
"GET,POST,PUT,DELETE,OPTIONS");
response.getHeaders().add("Access-Control-Allow-Headers",
"Authorization,Content-Type");
response.getHeaders().add("Access-Control-Max-Age", "3600");
break;
}
}
// OPTIONS 预检直接返回 200,不穿透到微服务
if (exchange.getRequest().getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return response.setComplete();
}
return chain.filter(exchange);
}
}
关键操作:OPTIONS 请求直接返回 200,不往下游传。 预检请求不需要微服务处理——它唯一的作用就是问"我能不能跨域",网关回答就行了。
后端的 CORS 要关干净
网关配完后,微服务侧必须把 CORS 关了。否则会出现"网关返回了一份 CORS 响应头,微服务又返回了一份,浏览器看到两份"的情况。
Spring Boot 里如果有 @CrossOrigin 注解或 WebMvcConfigurer 里的 CORS 配置,全部移除。或者设一个全局关闭:
// 不要留这个
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")... // ❌ 删掉
}
总结
CORS 是网关的职责,不是微服务的职责。
三步搞定:
- 网关统一 CORS ——
globalcors配置一份,所有后端共用 - 动态响应头 —— 不同路径匹配不同策略,OPTIONS 网关直接返回不穿透
- 后端关干净 —— 移除所有微服务的 CORS 配置,避免响应头冲突
配完之后,前端跨域问题从"有时报有时不报"变成"永远不报"。
有用的话转给还在每个 Controller 上贴 @CrossOrigin 的同事。
标题:Spring Cloud Gateway CORS 配置混乱:多服务重复定义跨域易冲突?网关统一拦截+动态响应头注入
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/06/28/1782635807058.html
公众号:服务端技术精选
