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

image

 前端的js文件如下

image

 啥叫跨域:

根据您提供的信息和问题,我来详细解释您遇到的“跨域问题”。

一、 您观察到的现象:为什么 curl 成功而浏览器失败?

您提到的 curl -k "http://gateway.test.com:9080/product/queryAllProduct?page=1&limit=10"能成功返回数据,但通过浏览器访问页面时却报错,这正是“跨域问题”的典型表现。原因在于:

  1. curl(命令行工具):它只是一个简单的HTTP客户端,没有“同源策略”限制。它可以直接向任何地址发送请求并接收响应,不关心请求的源和目标是否相同。

  2. 浏览器:浏览器运行着前端页面(JavaScript),出于安全考虑,它严格执行 “同源策略”​ 。当它发现页面来自 http://portal.test.com:9080,而页面中的JavaScript代码试图请求来自 http://gateway.test.com:9080的资源时,就会触发跨域检查并阻止请求。

简单类比

  • curl​ 像邮差,只要地址正确,就可以把信(请求)送到任何邮箱(服务器),并把回信(响应)带回来。

  • 浏览器​ 像一位严格的安保,规定“本小区(同源)的居民只能接收本小区邮局(同源服务器)的来信”。如果信来自其他邮局(不同源),即使邮差能送到,安保也会在居民拿到信之前拦截检查。只有对方邮局明确开具了“允许投递证明”(CORS响应头),安保才会放行。

二、 什么是“跨域”?其原理是什么?

  1. “同源”的定义:如果两个URL的协议(http/https)、域名(host)、端口(port)​ 三者完全相同,则视为“同源”。只要有一项不同,就是“跨源”(Cross-Origin),俗称“跨域”。

    • 例如:http://portal.test.com:9080http://gateway.test.com:9080域名不同,属于跨域。

  2. 浏览器的“同源策略”:这是浏览器最核心的安全基石之一。它规定,默认情况下,一个源(Origin)的脚本(JavaScript)无权访问另一个源的资源。这主要是为了防止恶意网站通过脚本窃取用户在其他网站(如银行、邮箱)的敏感数据。

  3. 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 Gatewaygateway.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等路径的请求代理到网关地址。这样对于浏览器来说,请求是发给“同源”的开发服务器,再由开发服务器转发,从而绕过浏览器的跨域检查。但这仅用于本地开发

针对您当前情况的总结与建议:

  1. 根本原因:您的浏览器前端(portal.test.com:9080)访问网关(gateway.test.com:9080)属于跨域请求,且网关的响应中没有包含必要的 Access-Control-Allow-Origin头部。

  2. 首选解决方案:请检查并修改您的 Spring Cloud Gateway 的配置文件,按照上述方案一添加CORS配置。

  3. 验证:配置生效后,在浏览器开发者工具的“网络(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相关的头部信息,从而允许跨域请求。

代码分析:

  1. 类上标注了@Configuration,表明这是一个配置类,会在Spring应用上下文初始化时被加载。

  2. 定义了一个WebFilter的Bean,名为corsFilter。WebFilter是Spring WebFlux中的过滤器,用于拦截请求和响应。

  3. 在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()),不再继续向下执行。否则,继续执行过滤器链。

  4. 另外,还定义了一个ServerCodecConfigurer的Bean,用于配置HTTP消息的编解码器。

  5. 定义了一个RouteDefinitionLocator的Bean,用于从服务注册中心(如Eureka)发现路由。这个Bean的作用是让网关能够动态地从服务注册中心获取服务实例,并自动创建路由。

这段代码通过上述方式解决了跨域问题,具体原理如下:

当浏览器发送跨域请求时,会先判断是否属于简单请求。如果不是简单请求,浏览器会先发送一个OPTIONS预检请求,询问服务器是否允许该跨域请求。服务器需要正确响应这个OPTIONS请求,并在响应头中包含CORS相关头部,告诉浏览器允许跨域。

在这个过滤器中,当收到跨域请求时,会在响应头中添加CORS相关头部。对于预检请求(OPTIONS),直接返回200,并且不会继续向下执行(即不会到达具体的业务处理)。对于非预检请求,在添加CORS头部后,会继续执行过滤器链,最终到达具体的路由处理。

这样,浏览器收到带有正确CORS头部的响应后,就会允许跨域请求,从而解决了跨域问题。

但是,这段代码存在一些潜在问题:

  1. 动态设置Access-Control-Allow-Origin为请求的Origin,这虽然方便,但可能存在安全风险。通常建议设置允许的源为白名单,而不是全部允许。

  2. 在设置Access-Control-Allow-Methods时,只设置了预检请求中请求头Access-Control-Request-Method指定的方法,而不是允许所有常用的HTTP方法。这可能导致某些跨域请求被拒绝。通常我们会设置允许的方法为:GET, POST, PUT, DELETE, OPTIONS等。

  3. 在设置Access-Control-Allow-Headers时,直接使用了请求头中的Access-Control-Request-Headers,这可能会允许一些不安全的头部。建议根据实际情况设置允许的头部。

  4. 在设置Access-Control-Expose-Headers时,使用了通配符"*",这可能会暴露不必要的响应头。建议根据实际需要暴露的响应头进行设置。

因此,在实际生产环境中,建议根据具体需求调整CORS配置,避免过于宽松的策略。

另外,需要注意的是,如果您的网关前面还有一层Nginx,可能需要在Nginx中也配置CORS,或者确保Nginx不会覆盖这些响应头。

总结:这段代码通过实现一个WebFilter,在响应中添加CORS头部,从而允许跨域请求。对于预检请求,直接返回200,避免继续向下执行。这样,浏览器就能正确收

posted on 2026-03-18 21:28  luzhouxiaoshuai  阅读(2)  评论(0)    收藏  举报

导航