十二、啥叫跨域:跨域的原理在浏览器中输入了http://portal.test.com:9080/页面,在该前面页面的js文件中去访问了网关的http://gateway.test.com:9080/product/queryAllProduct就是跨域

前端的js文件如下

啥叫跨域:
根据您提供的信息和问题,我来详细解释您遇到的“跨域问题”。
一、 您观察到的现象:为什么 curl 成功而浏览器失败?
您提到的 curl -k "http://gateway.test.com:9080/product/queryAllProduct?page=1&limit=10"能成功返回数据,但通过浏览器访问页面时却报错,这正是“跨域问题”的典型表现。原因在于:
-
curl(命令行工具):它只是一个简单的HTTP客户端,没有“同源策略”限制。它可以直接向任何地址发送请求并接收响应,不关心请求的源和目标是否相同。
-
浏览器:浏览器运行着前端页面(JavaScript),出于安全考虑,它严格执行 “同源策略” 。当它发现页面来自
http://portal.test.com:9080,而页面中的JavaScript代码试图请求来自http://gateway.test.com:9080的资源时,就会触发跨域检查并阻止请求。
简单类比:
-
curl 像邮差,只要地址正确,就可以把信(请求)送到任何邮箱(服务器),并把回信(响应)带回来。
-
浏览器 像一位严格的安保,规定“本小区(同源)的居民只能接收本小区邮局(同源服务器)的来信”。如果信来自其他邮局(不同源),即使邮差能送到,安保也会在居民拿到信之前拦截检查。只有对方邮局明确开具了“允许投递证明”(CORS响应头),安保才会放行。
二、 什么是“跨域”?其原理是什么?
-
“同源”的定义:如果两个URL的协议(http/https)、域名(host)、端口(port) 三者完全相同,则视为“同源”。只要有一项不同,就是“跨源”(Cross-Origin),俗称“跨域”。
-
例如:
http://portal.test.com:9080与http://gateway.test.com:9080域名不同,属于跨域。
-
-
浏览器的“同源策略”:这是浏览器最核心的安全基石之一。它规定,默认情况下,一个源(Origin)的脚本(JavaScript)无权访问另一个源的资源。这主要是为了防止恶意网站通过脚本窃取用户在其他网站(如银行、邮箱)的敏感数据。
-
CORS(跨源资源共享)机制:为了让合法的跨域请求能够进行,W3C制定了CORS标准。其核心原理是通过HTTP头部信息进行协商。
-
简单请求:对于某些安全的请求方法(如GET、POST)和内容类型,浏览器会直接发出请求,但在请求头中自动加入
Origin: http://portal.test.com:9080,声明请求来源。 -
服务器响应:服务器(如您的
gateway.test.com:9080)在收到请求后,如果需要允许该来源的访问,必须在响应头中包含:Access-Control-Allow-Origin: http://portal.test.com:9080(或使用通配符*)。 -
浏览器裁决:浏览器接收到响应后,会检查
Access-Control-Allow-Origin头部的值是否包含当前页面的源。如果包含,则允许前端JavaScript访问响应数据;如果不包含或根本没有这个头,浏览器就会抛出CORS错误,并阻止脚本获取响应内容,即使网络请求在技术上已经成功(状态码可能是200)。
-
在您的情况下,curl拿到了数据,但浏览器因为响应头中没有包含允许 portal.test.com:9080的CORS头部,所以拦截了响应,导致您的前端JavaScript报错“数据接口请求异常”。
三、 如何解决跨域问题?
解决思路的核心是:让服务器在响应中返回正确的CORS头部,告诉浏览器“我允许这个来源的请求”。根据您的架构,有几种主流方案:
方案一:在网关层统一配置CORS(推荐,最规范)
既然所有请求都通过 Spring Cloud Gateway(gateway.test.com:9080)转发,那么就在网关处统一处理CORS。这需要修改网关服务的配置(如application.yml)。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 对所有路径生效
allowed-origins:
- "http://portal.test.com:9080" # 允许您的前端源
# - "http://localhost:8080" # 开发时可能也需要
allowed-methods: "*" # 允许所有方法:GET, POST等
allowed-headers: "*" # 允许所有请求头
allow-credentials: true # 允许携带Cookie等凭证
配置完成后,重启网关服务。此后,所有通过网关转发的响应都会自动加上允许 portal.test.com:9080的CORS头部,浏览器就会放行。
方案二:在Nginx等反向代理层配置
如果请求先到达Nginx,再由Nginx转发给网关,也可以在Nginx配置中添加CORS头部。
方案三:后端微服务各自配置CORS(不推荐)
在每个微服务(如product-service)中添加CORS过滤器。但这会让配置分散,难以管理。
方案四:开发环境的前端代理
在开发阶段,可以配置前端开发服务器(如Vue CLI、webpack-dev-server)的代理功能,将 /api等路径的请求代理到网关地址。这样对于浏览器来说,请求是发给“同源”的开发服务器,再由开发服务器转发,从而绕过浏览器的跨域检查。但这仅用于本地开发。
针对您当前情况的总结与建议:
-
根本原因:您的浏览器前端(
portal.test.com:9080)访问网关(gateway.test.com:9080)属于跨域请求,且网关的响应中没有包含必要的Access-Control-Allow-Origin头部。 -
首选解决方案:请检查并修改您的 Spring Cloud Gateway 的配置文件,按照上述方案一添加CORS配置。
-
验证:配置生效后,在浏览器开发者工具的“网络(Network)”面板中,找到对
gateway.test.com的请求,查看其响应头(Response Headers),应该能看到Access-Control-Allow-Origin: http://portal.test.com:9080,同时前端页面功能应恢复正常。
请注意,您之前遇到的 ERR_NAME_NOT_RESOLVED错误是域名解析失败,是网络层问题,需要确保运行浏览器的机器能正确解析 gateway.test.com这个主机名(例如修改hosts文件)。在解决域名解析问题、请求能实际到达网关后,如果仍出现页面功能异常但curl正常的情况,那么就是这里所解释的CORS问题。
跨域的解决办法,在网关层面统一解决跨域的问题
/** * * LegendShop微服务商城系统 * * ©版权所有,并保留所有权利。 * */ package com.ctnrs.gateway.config; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator; import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties; import org.springframework.cloud.gateway.route.RouteDefinitionLocator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.support.DefaultServerCodecConfigurer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.cors.reactive.CorsUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; @Configuration public class CorsConfig { private static final String MAX_AGE = "18000L"; @Bean public WebFilter corsFilter() { return (ServerWebExchange ctx, WebFilterChain chain) -> { ServerHttpRequest request = ctx.getRequest(); if (CorsUtils.isCorsRequest(request)) { HttpHeaders requestHeaders = request.getHeaders(); ServerHttpResponse response = ctx.getResponse(); HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); HttpHeaders headers = response.getHeaders(); headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders()); if (requestMethod != null) { headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); } headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*"); headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); return Mono.empty(); } } return chain.filter(ctx); }; } /** * Server codec configurer. * * @return the server codec configurer */ @Bean public ServerCodecConfigurer serverCodecConfigurer() { return new DefaultServerCodecConfigurer(); } /** * 如果使用了注册中心(如:Eureka),进行控制则需要增加如下配置. * * @param discoveryClient the discovery client * @param properties the properties * @return the route definition locator */ @Bean public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) { return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties); } }
我们首先分析这段代码如何解决跨域问题。这段代码是一个Spring Cloud Gateway的CORS配置类,通过定义一个WebFilter来拦截请求,并在响应中添加CORS相关的头部信息,从而允许跨域请求。
代码分析:
-
类上标注了@Configuration,表明这是一个配置类,会在Spring应用上下文初始化时被加载。
-
定义了一个WebFilter的Bean,名为corsFilter。WebFilter是Spring WebFlux中的过滤器,用于拦截请求和响应。
-
在corsFilter方法中,返回一个WebFilter实例,该实例的过滤逻辑如下:
a. 获取当前请求(ServerHttpRequest)和响应(ServerHttpResponse)。
b. 使用CorsUtils.isCorsRequest(request)判断当前请求是否为跨域请求。如果是,则进行CORS处理。
c. 获取请求头中的Origin(请求来源)和Access-Control-Request-Method(预检请求的方法)等信息。
d. 在响应头中添加以下CORS相关头部:
-
Access-Control-Allow-Origin:允许的源,这里设置为请求头中的Origin值,即允许当前请求的源。
-
Access-Control-Allow-Headers:允许的请求头,这里设置为请求头中的Access-Control-Request-Headers值,即允许客户端请求中携带的头部。
-
Access-Control-Allow-Methods:允许的HTTP方法,如果请求头中有Access-Control-Request-Method,则将其设置为允许的方法。
-
Access-Control-Allow-Credentials:是否允许携带凭证,这里设置为true,表示允许携带cookie等凭证信息。
-
Access-Control-Expose-Headers:暴露给客户端的响应头,这里设置为*,表示所有响应头都暴露给客户端。
-
Access-Control-Max-Age:预检请求的缓存时间,这里设置为18000秒(5小时),表示在5小时内,同一跨域请求不再发送预检请求。
e. 如果当前请求是OPTIONS方法(即预检请求),则直接返回200(OK)状态码,并结束当前请求的处理(返回Mono.empty()),不再继续向下执行。否则,继续执行过滤器链。
-
-
另外,还定义了一个ServerCodecConfigurer的Bean,用于配置HTTP消息的编解码器。
-
定义了一个RouteDefinitionLocator的Bean,用于从服务注册中心(如Eureka)发现路由。这个Bean的作用是让网关能够动态地从服务注册中心获取服务实例,并自动创建路由。
这段代码通过上述方式解决了跨域问题,具体原理如下:
当浏览器发送跨域请求时,会先判断是否属于简单请求。如果不是简单请求,浏览器会先发送一个OPTIONS预检请求,询问服务器是否允许该跨域请求。服务器需要正确响应这个OPTIONS请求,并在响应头中包含CORS相关头部,告诉浏览器允许跨域。
在这个过滤器中,当收到跨域请求时,会在响应头中添加CORS相关头部。对于预检请求(OPTIONS),直接返回200,并且不会继续向下执行(即不会到达具体的业务处理)。对于非预检请求,在添加CORS头部后,会继续执行过滤器链,最终到达具体的路由处理。
这样,浏览器收到带有正确CORS头部的响应后,就会允许跨域请求,从而解决了跨域问题。
但是,这段代码存在一些潜在问题:
-
动态设置Access-Control-Allow-Origin为请求的Origin,这虽然方便,但可能存在安全风险。通常建议设置允许的源为白名单,而不是全部允许。
-
在设置Access-Control-Allow-Methods时,只设置了预检请求中请求头Access-Control-Request-Method指定的方法,而不是允许所有常用的HTTP方法。这可能导致某些跨域请求被拒绝。通常我们会设置允许的方法为:GET, POST, PUT, DELETE, OPTIONS等。
-
在设置Access-Control-Allow-Headers时,直接使用了请求头中的Access-Control-Request-Headers,这可能会允许一些不安全的头部。建议根据实际情况设置允许的头部。
-
在设置Access-Control-Expose-Headers时,使用了通配符"*",这可能会暴露不必要的响应头。建议根据实际需要暴露的响应头进行设置。
因此,在实际生产环境中,建议根据具体需求调整CORS配置,避免过于宽松的策略。
另外,需要注意的是,如果您的网关前面还有一层Nginx,可能需要在Nginx中也配置CORS,或者确保Nginx不会覆盖这些响应头。
总结:这段代码通过实现一个WebFilter,在响应中添加CORS头部,从而允许跨域请求。对于预检请求,直接返回200,避免继续向下执行。这样,浏览器就能正确收
posted on 2026-03-18 21:28 luzhouxiaoshuai 阅读(2) 评论(0) 收藏 举报
浙公网安备 33010602011771号