浏览器跨域
文章目录
一、什么是浏览器跨域?
浏览器跨域(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实现
-
使用 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); } }
-
实现 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接口来配置跨域请求处理这两种方法有以下区别:
- CorsFilter
CorsFilter是一个Servlet过滤器,它在请求进入Spring MVC处理器之前对请求进行预处理,并且可以对所有类型的HTTP请求(不仅仅是RESTful API)添加CORS响应头。
这种方式的配置更底层、更通用,适用于任何基于Servlet容器的应用,不局限于Spring MVC应用。
CorsFilter提供了更细粒度的控制,可以根据不同的URL路径或HTTP方法设置不同的跨域策略。 - 实现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内页面):
-
在子页面中监听来自父页面的消息事件:
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);
父页面:
-
创建iframe标签并设置其src属性为子页面地址:
<iframe id="childFrame" src="https://child.com/child-page.html"></iframe>
-
在父页面中通过postMessage向子页面发送消息:
var iframe = document.getElementById('childFrame'); // 发送一条消息给子页面 iframe.contentWindow.postMessage({ message: 'Hello from parent!' }, 'https://child.com');
-
同样需要在父页面中监听从子页面发来的消息:
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。这样既能确保安全性,又能满足一定的跨域通信需求。