浏览器跨域

浏览器跨域

一、什么是浏览器跨域?

浏览器跨域(Cross-Origin)

是指在Web应用中,当一个源(origin)试图访问或请求另一个不同源的资源时,由于浏览器的安全策略——同源策略(Same-Origin Policy)而受到限制的行为。

同源策略

同源策略是由浏览器执行的一种安全机制,它规定了来自同一源(即协议、域名和端口都相同)的文档或脚本可以与彼此交互。这里的“源”由三个部分组成:协议(如HTTP或HTTPS)、主机名(例如 example.com)和端口号(默认情况下HTTP是80,HTTPS是443)。如果这三个组成部分中有任何一个不匹配,就认为这两个URL属于不同的源。

跨域场景举例

从 https://www.example1.com 发起请求到 http://api.example2.com,因为协议不同,所以是跨域。
从 https://www.example1.com:443 发起到 https://api.example1.com:8080 的请求,因为端口号不同,也是跨域。

跨域限制的表现

XMLHttpRequest (AJAX) 请求无法发送至不同源的服务器。
Fetch API 请求同样受限于同源策略。
不允许通过JavaScript读取或修改其他源下的DOM内容。
不能访问不同源下的Cookies、LocalStorage等存储数据。

二、解决跨域的方案

1、跨域资源共享(CORS)

CORS是一种现代Web标准,允许服务器通过设置特定HTTP响应头来明确指定哪些源可以访问其资源。例如Access-Control-Allow-Origin、Access-Control-Allow-Methods和Access-Control-Allow-Headers等。

nginx实现

server {
    listen 80; # 监听HTTP端口(根据实际情况可能为443或其他端口)
    server_name yourdomain.com; # 替换为你的实际域名

    location /api/ { 
        # 如果希望只允许特定源发起的跨域请求,例如仅允许来自 https://app.example.com 的请求
        add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
        # 其他必要的CORS响应头设置
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With';
        # 如果需要支持携带凭证(cookies、Authorization等)的跨域请求
        add_header 'Access-Control-Allow-Credentials' 'true';

        # 预检请求(OPTIONS方法)处理
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }
        # 如果API服务部署在不同的服务器上,则添加反向代理配置
        proxy_pass http://backend-server:port;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

spring实现

  1. 使用 CorsFilter 实现跨域配置
    创建一个类实现 Filter 接口,并配置 doFilterInternal() 方法来添加 Access-Control-Allow-Origin 等响应头。

    
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class SimpleCORSFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            // 允许所有源发起请求
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
    
            if ("OPTIONS".equals(request.getMethod())) {
                response.setStatus(HttpServletResponse.SC_OK);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    }
    

    然后,在 Spring 的 Web 应用上下文配置文件(如:WebConfig.java)中注册这个过滤器:

    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    
    @Configuration
    public class WebConfig {
    
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true); // 允许携带凭证
            config.addAllowedOrigin("*"); // 允许所有源,根据实际情况可以改为具体的域名列表
            config.addAllowedHeader("*"); // 允许所有头部信息,可以根据实际需求设置特定的头部
            config.addAllowedMethod("*"); // 允许所有方法(GET、POST等)
            source.registerCorsConfiguration("/**", config); // 对所有的URL路径应用此配置
            return new CorsFilter(source);
        }
    }
    
  2. 实现 WebMvcConfigurer 接口
    在Spring MVC配置类中实现WebMvcConfigurer接口并重写addCorsMappings()方法进行跨域配置。

    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**") // 对所有的URL路径应用此配置
                    .allowedOrigins("*") // 允许所有源,也可以指定具体的域名列表
                    .allowedMethods("*") // 允许所有HTTP方法
                    .allowedHeaders("*") // 允许所有请求头
                    .allowCredentials(true); // 允许携带凭证(cookies、Authorization等)
        }
    }
    

在Spring框架中,通过CorsFilter和实现WebMvcConfigurer接口来配置跨域请求处理这两种方法有以下区别:

  1. CorsFilter
    CorsFilter是一个Servlet过滤器,它在请求进入Spring MVC处理器之前对请求进行预处理,并且可以对所有类型的HTTP请求(不仅仅是RESTful API)添加CORS响应头。
    这种方式的配置更底层、更通用,适用于任何基于Servlet容器的应用,不局限于Spring MVC应用。
    CorsFilter提供了更细粒度的控制,可以根据不同的URL路径或HTTP方法设置不同的跨域策略。
  2. 实现WebMvcConfigurer接口
    通过实现WebMvcConfigurer接口并重写addCorsMappings()方法来配置CORS,这种方式是专门为Spring MVC设计的,更加内聚和面向RESTful API服务。
    使用WebMvcConfigurer的方式将跨域配置集成到了Spring MVC的配置体系中,对于Spring Boot项目来说,代码结构更为清晰和统一。
    虽然WebMvcConfigurer也能提供基本的CORS配置,但相比于CorsFilter可能灵活性略低,因为它是针对MVC控制器的特定上下文。
    总结:
    如果您需要在非Spring MVC环境下或者需要非常灵活地为不同请求路径设定不同跨域策略时,推荐使用CorsFilter。
    对于主要关注Spring MVC RESTful服务的场景下,直接实现WebMvcConfigurer接口可能更加方便简洁。同时,在Spring Boot项目中,利用内置的自动配置功能,通过实现WebMvcConfigurer接口来配置CORS更为常见和简便。

2、JSONP (JSON with Padding)

JSONP适用于较老的浏览器,它利用 <script> 标签不受同源策略限制的特点进行数据获取。服务端返回一个JavaScript函数调用字符串,并将数据作为参数传递给预先定义好的客户端回调函数。JSONP仅支持GET请求,且安全性相对较低。

3、iframe + postMessage API

在父窗口和嵌入的iframe之间使用HTML5的window.postMessage()方法进行跨文档通信,这种方法可以在不同源之间安全地传递消息。

场景描述
假设您有一个主页面(父页面)在parent.com域名下,想要与嵌入的child.com子页面进行数据交互。
子页面(iframe内页面):

  1. 在子页面中监听来自父页面的消息事件:

    
    window.addEventListener('message', function(event) {
        // 检查消息来源是否可信(这里是示例,实际应根据需求验证)
        if (event.origin !== 'https://parent.com') return;
        
        // 获取父页面发送的数据
        var data = event.data;
    
        // 处理数据并执行相关操作
        processData(data);
        
        // 可以选择向父页面回复一个消息
        parent.postMessage({ response: 'Hello from child!' }, 'https://parent.com');
    }, false);
    

父页面:

  1. 创建iframe标签并设置其src属性为子页面地址:

    
    <iframe id="childFrame" src="https://child.com/child-page.html"></iframe>
    
  2. 在父页面中通过postMessage向子页面发送消息:

    
    var iframe = document.getElementById('childFrame');
    
    // 发送一条消息给子页面
    iframe.contentWindow.postMessage({ message: 'Hello from parent!' }, 'https://child.com');
    
  3. 同样需要在父页面中监听从子页面发来的消息:

    
    window.addEventListener('message', function(event) {
        if (event.origin !== 'https://child.com') return;
        
        // 获取子页面返回的数据
        var data = event.data.response;
        
        // 根据数据执行相应操作
        processResponse(data);
    }, false);
    

4、服务器反向代理

通过配置Nginx或其他HTTP服务器实现反向代理,前端应用的所有请求先经过代理服务器,由代理服务器转发到实际的目标服务器,然后目标服务器的响应再经由代理服务器回传给前端应用。这样从浏览器的角度看,所有的请求都像是来自同一源,从而避免了跨域问题。

nginx实现
server {
    listen 80; # 监听的端口(这里以HTTP为例,实际可能为443或自定义端口)
    server_name yourdomain.com; # 你的域名

    location /api/ { # 假设你希望对/api/路径下的请求进行反向代理
        proxy_pass http://backend-server:port; # 将请求转发至后端服务器地址及端口
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 设置CORS响应头,允许任何源访问(根据实际情况调整为具体的允许源列表)
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

        # 如果需要支持预检请求(OPTIONS方法),添加以下内容
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}
nginx最小实现
server {
    listen 80; # 监听的端口(这里以HTTP为例,实际可能为443或自定义端口)
    server_name yourdomain.com; # 你的域名

    location /api/ { # 假设你希望对/api/路径下的请求进行反向代理
        proxy_pass http://backend-server:port; # 将请求转发至后端服务器地址及端口
        # 设置CORS响应头,允许任何源访问(根据实际情况调整为具体的允许源列表)
        add_header 'Access-Control-Allow-Origin' '*'; 
        #示例格式add_header 'Access-Control-Allow-Origin' 'https://yourapp.com, https://yourotherapp.com';
    }
}

为什么配置了反向代理还要加’Access-Control-Allow-Origin’?

当使用Nginx作为反向代理时,从浏览器的角度来看确实已经不再发生跨域访问。但Nginx与后端服务之间仍然是跨域的。虽然浏览器不知道也不关心这一层关系,但为了保证整个过程中所有环节都符合CORS规范,通常仍然会在Nginx配置中设置Access-Control-Allow-Origin等CORS响应头。
这些响应头信息会随着Nginx转发的响应返回给浏览器,从而让浏览器知道后端服务允许哪些源进行跨域访问。尤其在涉及到复杂请求、预检请求(OPTIONS方法)以及携带认证信息(如Cookie)的情况下,正确设置CORS响应头至关重要。即使在使用Nginx代理时从浏览器到Nginx的部分看似不跨域,但在整体架构上考虑,这样的设置有助于保持系统的兼容性和可扩展性。

5、其他解决方案

  • WebSockets协议支持跨域可以通过设置HTTP头部信息如Origin和Sec-WebSocket-Protocol处理跨域请求。

  • 对于一些特殊情况,如主域名相同但子域名不同的场景,可以通过设置document.domain属性配合iframe实现有限的跨域通信。

  • 针对静态资源的预加载技术(如)虽然不能直接解决跨域问题,但在某些情况下可以优化跨域资源的加载速度。

在现代Web开发中,CORS仍然是最常用也最符合Web标准的跨域解决方案。同时,结合具体应用场景和技术需求,可能会采用上述其他方案中的某一种或多种组合使用。

三、为什么配置nginx反向代理后,仍然无法查看或修改跨域页面的dom

因为浏览器的安全机制是独立于网络层的。同源策略不仅作用于HTTP请求,而且对JavaScript访问和操作页面(包括iframe)内的DOM内容也有限制。

当涉及到不同源之间的DOM操作时,例如顶级窗口尝试访问或修改嵌入的iframe中的DOM元素,浏览器会检查这些DOM元素所属的文档与当前执行脚本所在的文档是否同源(即协议、域名和端口都相同)。即使通过Nginx反向代理加载了跨域页面,使得HTTP请求看起来像是同源,但浏览器在解析并执行JavaScript时仍然能够识别出两个页面来自不同的原始地址,因此它依然会阻止非同源的DOM操作。

后,仍然无法查看或修改跨域页面的dom

因为浏览器的安全机制是独立于网络层的。同源策略不仅作用于HTTP请求,而且对JavaScript访问和操作页面(包括iframe)内的DOM内容也有限制。

当涉及到不同源之间的DOM操作时,例如顶级窗口尝试访问或修改嵌入的iframe中的DOM元素,浏览器会检查这些DOM元素所属的文档与当前执行脚本所在的文档是否同源(即协议、域名和端口都相同)。即使通过Nginx反向代理加载了跨域页面,使得HTTP请求看起来像是同源,但浏览器在解析并执行JavaScript时仍然能够识别出两个页面来自不同的原始地址,因此它依然会阻止非同源的DOM操作。

要实现不同源间的DOM交互,通常需要使用Web API提供的安全通信方法,如window.postMessage(),通过消息传递的方式来间接进行数据交换,而不是直接操作DOM。这样既能确保安全性,又能满足一定的跨域通信需求。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值