微服务跨域问题|The 'Access-Control-Allow-Origin' header contains multiple values

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


参考

Spring Cloud Gateway CORS 方案看这篇就够了 - 腾讯云开发者社区 - 腾讯云

九种跨域方式实现原理(完整版) - 掘金

什么是跨域,有哪些解决方法

以下为 AI 回答

跨域是指由于浏览器的安全机制,当一个页面尝试通过 XMLHttpRequest 或 Fetch API 等方式向不同于当前页面源(协议、域名、端口三者之一不同的 URL)发起请求时,会被浏览器阻止,这称为 “同源策略” 限制。这是为了防止恶意网站通过脚本对其他网站的数据进行非法访问。

解决跨域问题的方法有很多种,以下是常见的几种:

1、CORS(Cross-Origin Resource Sharing)

CORS 是一种 W3C 标准,允许服务器表明哪些其他源可以请求资源。服务器通过设置响应头(如 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers 等)来允许特定源或者所有源进行跨域请求。客户端(浏览器)在接收到响应后判断是否符合 CORS 规则,若符合则允许数据交互。

2、JSONP(JSON with Padding)

JSONP 是一种非正式传输协议,它利用 script 标签不受同源策略限制的特点,通过回调函数的形式来加载跨域数据。JSONP 只支持 GET 请求,并且要求服务器端配合提供 JSONP 形式的响应。

3、代理服务器(Proxy Server)

通过在服务器端设置一个代理,使得所有的请求都先经过这个代理服务器,然后由代理服务器转发给目标服务器,这样在浏览器看来,始终是在同一个源下进行通信。

4、document.domain + iframe

对于两个同顶级域名下的页面,可以通过修改各自的 document.domain 属性来允许它们之间的通信。

5、window.postMessage()

HTML5 提供了 window.postMessage 方法,允许来自不同源的脚本采用异步方式进行有限的通信,可以在窗口间发送消息。

6、WebSockets

虽然 WebSocket 连接建立之初也会遇到同源策略限制,但它本身提供了机制来处理跨域连接,服务器可以在握手阶段设置 Origin 头部来控制哪些源可以建立连接。

现代 web 开发中,CORS 是最常用的跨域解决方案,因为它支持更广泛的请求类型(不仅仅是 GET)和更安全的通信模式。然而,在某些特定场景下,如兼容旧版浏览器或者简单的 GET 请求,JSONP 仍然是一个可行的选择。

Spring Cloud Gateway 微服务跨域问题

当我们在网关和子服务都配置了 CORS 允许特定源的跨域请求时,还是会出现以下问题。

The 'Access-Control-Allow-Origin' header contains multiple values '*, http://localhost:5173', but only one is allowed.

从提示来看,是 Access-Control-Allow-Origin 头包含了多个值。

果然,Access-Control-Allow-Origin 确实包含了多个值,这就是问题根源,具体的分析可以参考 Spring Cloud Gateway CORS 方案看这篇就够了 - 腾讯云开发者社区 - 腾讯云文章。

解决方案

既然是重复配置了,那么去掉一个就行了,如下是我的子服务和网关的配置,我是删去了子服务的配置。

当然还有一种方法是在 response 中去除重复 header 的。

子服务跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author wnhyang
* @date 2024/3/14
**/
@Configuration
@Slf4j
public class WebConfig {

/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter() {
log.info("[CorsFilter][初始化corsFilter配置]");
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}

网关跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 跨域配置
*
* @author wnhyang
* @date 2023/8/25
*/
@Component
public class GlobalCorsFilter implements WebFilter, Ordered {

/**
* 这里为支持的请求头,如果有自定义的header字段请自己添加
*/
private static final String ALLOWED_HEADERS = "X-Requested-With, Content-Language, Content-Type, Authorization, credential, X-XSRF-TOKEN, isToken, token, Admin-Token, App-Token";
private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD";
private static final String ALLOWED_ORIGIN = "*";
private static final String ALLOWED_EXPOSE = "*";
private static final String MAX_AGE = "18000L";

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Credentials", "true");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(exchange);
}

@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview