最近在帮团队排查一个Spring Cloud项目时,发现前端同事在Swagger调试接口时频繁遇到"Failed to fetch"错误。控制台里醒目的CORS报错让人头疼不已——明明在Gateway里配置了allowedOrigins: "*",为什么还会出现跨域问题?这其实是很多开发者都会踩的坑:通配符配置在特定场景下会完全失效。
跨域问题本质上是个浏览器安全机制。当你的前端应用运行在http://localhost:3000,而接口服务部署在http://api.example.com时,浏览器会阻止这种"跨源"请求。Spring Cloud Gateway作为微服务入口,正是处理跨域的最佳位置。但常见的三种错误配置是:
*却需要携带Cookie等凭证信息我去年重构的一个电商项目就遇到过这种情况:Swagger能正常显示接口文档,但点击Try it out就会报跨域错误。后来发现是因为网关配置的Access-Control-Allow-Headers漏掉了Authorization,导致携带Token的请求被浏览器拦截。
很多教程会教你这样配置:
yaml复制spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
这在简单场景下确实能工作,但存在严重安全隐患。根据MDN官方文档要求,当请求需要携带Cookie、Authorization等凭证时,服务器必须指定具体域名而非通配符。这就是为什么你的登录接口总是跨域失败。
更安全的配置应该是:
yaml复制allowedOrigins: "https://your-frontend.com"
allowedCredentials: true
exposedHeaders: "X-Custom-Header"
浏览器在发送实际请求前,会先发一个OPTIONS预检请求。很多开发者只处理了GET/POST却忽略了这点。在Gateway中可以通过路由配置自动处理:
java复制@Bean
public WebFilter corsFilter() {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (CorsUtils.isPreFlightRequest(request)) {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Origin", "https://trusted.com");
headers.add("Access-Control-Allow-Methods", "GET,POST");
headers.add("Access-Control-Max-Age", "3600");
return Mono.empty();
}
return chain.filter(exchange);
};
}
默认情况下,浏览器只能访问以下简单响应头:
如果你的接口返回了自定义头如X-Auth-Token,必须显式配置:
yaml复制exposedHeaders: "X-Auth-Token, X-Custom-Header"
硬编码域名在多地部署时会很麻烦。我们可以通过自定义过滤器实现动态校验:
java复制public class DynamicCorsFilter implements WebFilter {
private final List<String> allowedOrigins = Arrays.asList(
"https://production.com",
"http://localhost:3000"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String origin = exchange.getRequest().getHeaders().getOrigin();
if (allowedOrigins.contains(origin)) {
exchange.getResponse().getHeaders()
.add("Access-Control-Allow-Origin", origin);
exchange.getResponse().getHeaders()
.add("Access-Control-Allow-Credentials", "true");
}
return chain.filter(exchange);
}
}
建议在application.yml中区分环境配置:
yaml复制spring:
profiles: dev
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "http://localhost:3000"
allowedMethods: "GET,POST"
---
spring:
profiles: prod
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://production.com"
allowedMethods: "GET,POST,PUT,DELETE"
Swagger UI在调试时会产生两次请求:文档请求和实际API请求。需要特别注意:
推荐配置:
java复制@Bean
public WebFluxConfigurer swaggerCorsConfigurer() {
return new WebFluxConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/v3/api-docs/**")
.allowedOrigins("*")
.allowedMethods("GET");
}
};
}
当遇到跨域问题时,按F12打开开发者工具:
典型错误包括:
No 'Access-Control-Allow-Origin' header:未配置或域名不匹配Credentials not supported with wildcard origin:用了*却需要凭证Method PUT is not allowed:漏配了请求方法启用DEBUG日志查看请求处理过程:
properties复制logging.level.org.springframework.cloud.gateway=DEBUG
logging.level.reactor.netty.http.client=DEBUG
编写单元测试验证CORS配置:
java复制@Test
void testCorsConfiguration() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://safe-origin.com");
config.addAllowedMethod("POST");
assertThat(config.checkOrigin("https://safe-origin.com")).isTrue();
assertThat(config.checkOrigin("http://attacker.com")).isNull();
}
不要盲目允许所有方法:
yaml复制allowedMethods: "GET,POST,PUT"
精确指定允许的请求头:
yaml复制allowedHeaders: "Content-Type,Authorization,X-Requested-With"
预检请求结果缓存时间不宜过长:
yaml复制maxAge: 1800 # 30分钟
即使配置了CORS,仍需防范CSRF攻击:
java复制http.csrf(csrf -> csrf
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
.requireCsrfProtectionMatcher(
request -> request.getMethod() != HttpMethod.GET.name()
)
);
在实际项目中,我建议建立一个CORS配置检查清单,每次发版前确认: