跨域通信机制详解

引言

在 Web 开发中,跨域问题 是开发者频繁遇到的拦路虎。浏览器出于安全考虑,通过同源策略限制了不同源之间的资源访问,这为前后端分离的架构带来挑战。本文将系统性解析跨域问题的原理、传统解决方案及现代标准的实现细节,并提供生产环境中的最佳实践。


一、同源策略的底层原理

1.1 同源策略的定义

同源策略是浏览器中最重要的安全机制之一,用于限制不同源之间的交互,防止恶意网站窃取用户数据或发起攻击。它确保了用户的敏感信息(如Cookie、LocalStorage等)不会被其他源的脚本非法访问。 同源策略要求以下三要素完全一致,否则即被视为跨域:

当前页面url 被请求页面 是否跨域 原因
https://www.baidu.com/ https://www.baidu.com/news 同源(协议、域名、端口相同)
http://localhost:3000 http://192.168.3.1:3000 跨域 域名不同
http://localhost:3000 http://localhost:5000 跨域 端口不同
https://a.com http://a.com 跨域 协议不同
https://www.test.com https://www.baidu.com 跨域 主域不同(test/baidu)
https://a.test.com https://b.test.com 跨域 子域不同(a/b)

1.2 同源策略到底拦了什么?

场景 能否访问 备注
AJAX / Fetch 请求 需 CORS 预检或代理
Cookie / LocalStorage / IndexedDB 按源隔离
DOM 访问 拿不到子页面 document
图片、CSS、JS 资源 标签天然放行,但拿不到内容
WebSocket 协议层面无同源限制

可以用一句话总结:同源策略破坏的是"响应数据",不破坏"请求本身"。 后端服务器依旧能收到请求,只是浏览器把数据扣下了。


二、跨域解决方案

2.1 JSONP ------ 传统解决方案

JSONP 利用 <script> 标签不受同源策略影响的特性,实现跨域请求。

前端动态创建 <script> 标签,src指向目标接口,并附加回调函数名参数(如callback=handleResponse)传递给后端来请求资源。前端提前定义好以参数为名的函数体,来获取响应数据。后端将该参数处理成函数调用的写法并传入要返回的数据,例如:handleResponse({data: 'Hello'}) 。回调函数执行,解析响应数据。
示例代码(前端):

js 复制代码
function handleData(res) {
  console.log(res);
}
const script = document.createElement('script');
script.src = 'https://api.xxx.com/data?callback=handleData';
document.body.appendChild(script);

缺陷:

  • 仅支持 GET 请求:无法处理 POST/PUT 等方法,且参数需附加在 URL 中。
  • 依赖后端支持 :后端需预设对 callback 参数的处理逻辑。

2.2 CORS(官方标准,生产首选)

CORS 全称是"跨域资源共享"。它允许浏览器向不同源的服务器,发出 XMLHttpRequest 请求。 CORS 通过预检请求(Preflight) 和响应头配置,实现安全的跨域通信。

后端设置响应头,告诉浏览器"我允许这个网站访问我的资源"。

响应头 作用
Access-Control-Allow-Origin 允许访问的源(可设为具体域名或 * ,但 * 不兼容其他安全头)
Access-Control-Allow-Methods 允许的 HTTP 方法(如GET, POST, PUT)
Access-Control-Allow-Headers 允许自定义的请求头
Access-Control-Allow-Credentials 是否允许携带凭证(Cookie等,默认false)

有一些请求对服务器有着特殊的要求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json。非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求 ( OPTIONS 请求方法) ,称为 "预检"请求 。其作用在于,确认当前网页所在的域名是否在服务器的许可名单中,明确可使用的 HTTP 请求方法和头信息字段。只有在这个请求返回成功的情况下,浏览器才会发出正式的请求。
后端响应示例:

js 复制代码
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://client.example.com'); // 允许的源
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Credentials', 'true'); // 允许携带凭证

  if (req.method === 'OPTIONS') {
    res.sendStatus(200); // 预检请求直接返回成功
  } else {
    next();
  }
});

2.3 Nginx 反向代理

Nginx 实现原理类似于 Node 中间件代理,需要搭建一个中转 nginx 服务器,用于转发请求。即利用 nginx 反向代理功能,将请求转发到后端服务器,后端服务器收到请求后,将响应返回给 nginx,nginx 再将响应返回给前端浏览器。它是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session ,不需要修改任何代码,并且不会影响服务器性能。


2.4 WebSocket ------ 实时通信

WebSocket 是一种基于 TCP 的双向通信协议,允许客户端和服务器之间进行实时、全双工通信。 首先启动一个 HTTP 服务器,等待客户端发起连接请求。当客户端请求 WebSocket 连接时,服务器通过 HTTP 101状态码切换协议,升级为 WebSocket。一旦协议升级成功,服务器和客户端就可以通过 WebSocket 进行通信。双方通过特定格式的数据帧进行消息的发送与接收。


2.5. postMessage(iframe 场景)

当一个页面通过 iframe 标签内嵌了一个二级页面 ,且一级页面和二级页面要进行通信时,默认是跨域的。利用 window.postMessage() 方法实现跨域通信postMessage() 方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

js 复制代码
otherWindow.postMessage(message, targetOrigin,[transfer]);
  • message: 将要发送到其他 window 的数据。
  • targetOrigin: 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串*(表示无限制)或者一个 URL。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
  • transfer(可选):是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

2.6 document.domain

该方法只适合子域相同,主域不同 的情况,在两个页面都设置 document.domain='子域'。 比如 a.test.comb.test.com 适用于该方式,只需要给页面添加 document.domain ='test.com',表示二级域名都相同就可以实现跨域。 两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。

注意:自Chrome 101版本起,该功能已被废弃,设置操作将导致属性变为可读属性且不再生效。

相关推荐
skeletron20119 分钟前
【基础】React工程配置(基于Vite配置)
前端
怪可爱的地球人11 分钟前
前端
蓝胖子的小叮当19 分钟前
JavaScript基础(十四)字符串方法总结
前端·javascript
跟橙姐学代码1 小时前
Python 函数实战手册:学会这招,代码能省一半!
前端·python·ipython
森之鸟1 小时前
审核问题——鸿蒙审核返回安装失败,可以尝试云调试
服务器·前端·数据库
jiayi1 小时前
从 0 到 1 带你打造一个工业级 TypeScript 状态机
前端·设计模式·状态机
轻语呢喃1 小时前
CSS水平垂直居中的9种方法:原理、优缺点与差异对比
前端·css
!win !1 小时前
uni-app支付宝端彻底禁掉下拉刷新效果
前端·小程序·uni-app
xw51 小时前
uni-app支付宝端彻底禁掉下拉刷新效果
前端·支付宝
@大迁世界2 小时前
这次 CSS 更新彻底改变了我的 CSS 开发方式。
前端·css