微服务跨域问题|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的。

子服务跨域配置

java 复制代码
/**
 * @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);
    }
}

网关跨域配置

java 复制代码
/**
 * 跨域配置
 *
 * @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

相关推荐
海绵波波1071 小时前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
网络风云2 小时前
【魅力golang】之-反射
开发语言·后端·golang
Q_19284999062 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
运维&陈同学4 小时前
【Kibana01】企业级日志分析系统ELK之Kibana的安装与介绍
运维·后端·elk·elasticsearch·云原生·自动化·kibana·日志收集
Javatutouhouduan6 小时前
如何系统全面地自学Java语言?
java·后端·程序员·编程·架构师·自学·java八股文
后端转全栈_小伵6 小时前
MySQL外键类型与应用场景总结:优缺点一目了然
数据库·后端·sql·mysql·学习方法
编码浪子7 小时前
Springboot高并发乐观锁
后端·restful
uccs7 小时前
go 第三方库源码解读---go-errorlint
后端·go
Mr.朱鹏8 小时前
操作002:HelloWorld
java·后端·spring·rabbitmq·maven·intellij-idea·java-rabbitmq
编程洪同学9 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端