大宇宇宇
发布于 2025-08-30 / 17 阅读
1
0

解决跨域的方式

#JS

跨域问题是浏览器出于安全考虑实施的同源策略(Same-Origin Policy)导致的。当协议、域名或端口三者中有一个不同时,就属于跨域。浏览器会阻止页面中发起的跨域请求(如 AJAX 请求)或读取跨域资源(如 iframe、字体等)。

以下是解决跨域问题的常见方式,各有适用场景和优缺点:


🧩 1. CORS (Cross-Origin Resource Sharing) - 跨域资源共享 (最推荐)

  • 原理: 这是由 W3C 制定的标准解决方案。服务器在 HTTP 响应头中添加特定的 Access-Control-Allow-* 字段,明确告知浏览器哪些源(Origin)可以访问该资源,以及允许哪些方法、请求头等。

  • 实现:

    • 服务器端配置: 这是最关键的一步。服务器需要根据请求的 Origin 头(浏览器自动添加)来设置响应头。

      • Access-Control-Allow-Origin: 必需。指定允许访问的源。可以是具体域名(如 https://example.com),也可以是 *(表示允许所有域,但不能与凭证请求 withCredentials: true 同时使用)。

      • Access-Control-Allow-Methods: 允许的 HTTP 方法(如 GET, POST, PUT, DELETE)。

      • Access-Control-Allow-Headers: 允许客户端发送的自定义请求头(如 X-Requested-With, Content-Type)。

      • Access-Control-Allow-Credentials: 是否允许发送 Cookie(truefalse)。如果设为 trueAccess-Control-Allow-Origin 不能*

      • Access-Control-Max-Age: 预检请求(Preflight Request)的结果可以被缓存多久(秒)。

    • 客户端: 对于简单请求(如 GET/HEAD/POST 且满足特定 Content-Type),浏览器会自动带上 Origin 头并发送请求。对于非简单请求(如 PUT/DELETE 或带自定义头),浏览器会先发送一个 OPTIONS 预检请求,服务器响应允许后,浏览器才会发送实际请求。现代浏览器(如 Fetch API, Axios)默认支持 CORS,无需特殊代码(除非需要处理凭证)。

  • 优点:

    • 标准、安全、灵活: 是官方标准,控制粒度细(可指定域名、方法、头、凭证)。

    • 支持所有 HTTP 方法: GET, POST, PUT, DELETE, PATCH 等都支持。

    • 现代浏览器广泛支持。

  • 缺点:

    • 需要服务器端配合修改配置: 如果无法修改目标服务器配置,此方法无效。

    • 非简单请求会多一次预检请求(OPTIONS),有轻微性能开销。

  • 适用场景: 首选方案,适用于你能控制目标服务器配置或服务器已支持 CORS 的情况。是现代 Web 应用开发的标准实践。


🧩 2. JSONP (JSON with Padding) - (已过时,仅了解)

  • 原理: 利用 <script> 标签的 src 属性不受同源策略限制的特性。动态创建一个 <script> 标签,其 src 指向目标服务器的一个接口,并在 URL 中携带一个回调函数名(如 callback=handleResponse)。服务器返回的不是纯 JSON,而是将 JSON 数据包裹在这个回调函数调用中的 JavaScript 代码(如 handleResponse({"data": "value"}))。浏览器加载并执行这段 JS,从而触发回调函数处理数据。

  • 实现:

    • 客户端: 动态创建 <script> 标签,定义全局回调函数。

    • 服务器端: 检查 URL 中的回调参数名,将返回数据包裹在 回调函数名(数据) 的格式中。

  • 优点:

    • 兼容性好: 支持非常老的浏览器。

    • 实现相对简单(对于简单的 GET 请求)。

  • 缺点:

    • 仅支持 GET 请求: 无法实现 POST, PUT 等其他方法。

    • 安全性差: 容易被 XSS 攻击(如果返回的数据不可信或回调函数名被篡改)。

    • 服务器需要专门支持 JSONP 格式。

    • 错误处理困难: 无法像 XHR 一样监听 HTTP 错误状态码(如 404, 500)。

  • 适用场景: 已不推荐使用。仅适用于无法使用 CORS 且目标服务器只支持 JSONP 的非常老旧的系统,且仅用于 GET 请求。新项目应避免使用。


🧩 3. 代理服务器 (Proxy Server)

  • 原理: 在浏览器和目标服务器之间设置一个同源的代理服务器(中间人)。浏览器将请求发送到同源的代理服务器,代理服务器再将请求转发到真正的目标服务器(跨域),目标服务器响应给代理服务器,代理服务器再将响应返回给浏览器。由于浏览器始终是向同源的代理服务器发起请求,因此不会触发跨域限制。

  • 实现:

    • 开发环境:

      • Webpack Dev Server / Vite:devServer 配置中设置 proxy,非常方便。例如:

// webpack.config.js
module.exports = {
  // ...
  devServer: {
    proxy: {
      '/api': { // 以 /api 开头的请求
        target: 'http://target-server.com', // 真正的目标服务器
        changeOrigin: true, // 必须设置,改变请求头中的 Host 为目标服务器
        pathRewrite: { '^/api': '' } // 可选,重写路径,去掉 /api 前缀
      }
    }
  }
};
  • Node.js 中间件: 使用 http-proxy-middleware 等库在 Express/Koa 等框架中配置代理。

  • 生产环境:

    • Nginx 反向代理: 最常用、性能好的方案。配置 Nginx 的 location 块,使用 proxy_pass 指令转发请求。

server {
    listen 80;
    server_name your-proxy-domain.com;

    location /api/ {
        proxy_pass http://target-server.com/; # 转发到目标服务器
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        # 其他代理头设置...
    }
}
  • 云服务商提供的代理服务: 如 AWS API Gateway, Cloudflare Workers 等。

  • 优点:

    • 浏览器端无感知: 前端代码无需修改,像请求同源接口一样写代码。

    • 可以隐藏真实目标服务器: 提高安全性。

    • 可以统一处理认证、日志、缓存等。

    • 支持所有 HTTP 方法和复杂请求。

  • 缺点:

    • 需要搭建和维护代理服务器: 增加了系统复杂度和运维成本(尤其在生产环境)。

    • 所有请求多经过一跳,可能增加延迟(但通常很小)。

  • 适用场景:

    • 开发环境: 非常常用,解决本地开发时的跨域问题。

    • 生产环境: 当无法或不想修改目标服务器配置(如第三方 API),或者需要统一管理、保护后端服务时。Nginx 是生产环境的首选。


🧩 4. WebSocket

  • 原理: WebSocket 协议本身在设计上就不受同源策略的限制。浏览器建立 WebSocket 连接时,会发送一个 Origin 头,服务器可以根据这个头决定是否接受连接。一旦连接建立,双工通信就畅通无阻。

  • 实现:

    • 客户端: 使用 new WebSocket(url) 建立连接,url 可以是跨域的(如 wss://target-server.com/ws)。

    • 服务器端: 在握手阶段(HTTP Upgrade 请求)检查 Origin 头,决定是否返回 101 Switching Protocols 响应。连接建立后,通过 WebSocket 协议收发消息。

  • 优点:

    • 天然支持跨域: 协议设计如此。

    • 实时双向通信: 适合需要实时数据交换的场景。

  • 缺点:

    • 主要用于实时通信: 不是为普通的 HTTP 请求/响应设计的。

    • 服务器需要实现 WebSocket 协议和 Origin 验证逻辑。

  • 适用场景: 需要实时双向通信的应用(如聊天室、实时数据看板、在线协作),且需要跨域时。


🧩 5. document.domain + iframe (已过时/受限)

  • 原理: 适用于主域相同但子域不同的页面(如 a.example.comb.example.com)。将这两个页面的 document.domain 都设置为相同的主域(如 example.com),浏览器就会认为它们同源,从而允许它们通过 parentcontentWindow 等属性互相访问。

  • 实现:

    • 在父页面(如 a.example.com)和 iframe 子页面(如 b.example.com)的 JavaScript 代码中,都添加:

document.domain = 'example.com';

  • 之后就可以像同源页面一样互相访问 DOM 和调用方法。

  • 优点:

    • 对于特定场景(同主域不同子域)实现简单。

  • 缺点:

    • 仅限主域相同的情况: 完全不同的域名(如 example.comanother.com)无效。

    • 安全风险: 设置 document.domain 会降低页面的隔离性,增加 XSS 攻击风险。

    • 现代浏览器限制严格: Chrome 等浏览器已限制或废弃此方法,或要求页面同时设置 document.domainOrigin-Agent-Cluster 头,使用起来非常不便且不可靠。

  • 适用场景: 已不推荐使用。仅存在于一些非常老旧的系统或特定受限环境中。应优先使用 postMessage


🧩 6. postMessage API

  • 原理: HTML5 引入的安全的跨文档/跨窗口通信方法。允许不同源的窗口(包括 iframe)之间通过 window.postMessage() 发送消息,并通过监听 message 事件来接收消息。发送方可以指定目标窗口的 origin,接收方可以验证消息来源的 origin

  • 实现:

    • 发送消息 (父页面 -> iframe 或 iframe -> 父页面):

// 发送方 (例如父页面)
const iframeWindow = document.getElementById('myIframe').contentWindow;
const targetOrigin = 'https://target-domain.com'; // 必须明确指定目标源,或 '*'(不安全)
iframeWindow.postMessage('Hello from parent!', targetOrigin);

// 或在 iframe 内发送给父窗口
window.parent.postMessage('Hello from iframe!', 'https://parent-domain.com');

接收消息:

// 接收方 (父页面或 iframe)
window.addEventListener('message', (event) => {
  // **安全检查:验证来源!**
  if (event.origin !== 'https://expected-sender-domain.com') {
    return; // 忽略来自未知源的消息
  }
  console.log('Received message:', event.data);
  // 处理消息...
});
  • 优点:

    • 安全可控: 通过指定和验证 origin,可以精确控制通信来源。

    • 支持不同源: 解决了 iframe 跨域通信的核心问题。

    • 支持传递复杂数据: 可以传递字符串、对象(会被结构化克隆)等。

  • 缺点:

    • 仅用于窗口/iframe 间的通信: 不是用于解决 AJAX 请求跨域的。

    • 需要双方配合实现发送和接收逻辑。

  • 适用场景: 解决不同源页面(主要是 iframe)之间通信问题的标准方案。例如嵌入第三方内容(如地图、支付、登录框)时,主页面和嵌入内容之间的数据交互。


🧩 7. 其他(已过时/不推荐)

  • window.name 利用 window.name 在页面加载后保持不变且容量大的特性,通过 iframe 和同源代理页面传递数据。过程复杂,安全性差,已淘汰。

  • location.hash / URL Fragment 利用 URL 的 # 后部分(hash)变化不会触发页面刷新的特性,通过监听 hashchange 事件传递简单数据。仅限单向、少量数据传递,且 URL 可见,不安全。已淘汰。

  • Flash 跨域策略文件 (crossdomain.xml): 依赖 Flash 插件,Flash 已被淘汰,此方法不再使用。


📌 总结与选择建议

方法

原理/关键点

优点

缺点/限制

适用场景

推荐度

CORS

服务器设置 Access-Control-Allow-*

标准、安全、灵活、支持所有方法

需服务器配置;非简单请求有预检开销

首选方案,能控制服务器时

⭐⭐⭐⭐⭐

代理服务器

同源代理转发请求

浏览器无感知;可统一管理;支持所有方法

需搭建/维护代理;增加延迟

开发环境;生产环境无法改目标服务器时

⭐⭐⭐⭐

WebSocket

协议天然支持跨域

天然支持跨域;实时双向通信

仅用于实时通信;需服务器实现协议

需要实时双向通信且跨域时

⭐⭐⭐

postMessage

窗口间消息传递,验证 origin

安全可控;解决 iframe 跨域通信

仅用于窗口/iframe 通信;需双方配合

解决 iframe 跨域通信的标准方案

⭐⭐⭐⭐

JSONP

动态 <script> 标签 + 回调函数

兼容老浏览器

仅 GET不安全;需服务器支持;错误处理难

已过时,仅限无法用 CORS 的老系统 GET 请求

document.domain

设置相同主域

主域相同子域不同时简单

仅限主域相同安全风险浏览器限制严

已过时/受限,避免使用

其他 (window.name, location.hash, Flash)

Hack 手段

(无显著优点)

复杂、不安全、已淘汰

绝对避免使用

🎯 如何选择?

  1. 首选 CORS: 如果你能控制目标服务器(或说服对方运维),CORS 是最标准、最安全、最灵活的解决方案。这是现代 Web 开发的基石。

  2. 开发环境用代理: 本地开发时,使用 Webpack Dev Server / Vite 的代理功能是最方便快捷的方式。

  3. 生产环境代理: 如果目标服务器是第三方 API 无法修改,或者你需要统一管理入口、增强安全性,使用 Nginx 等反向代理是生产环境的常用且可靠方案。

  4. iframe 通信用 postMessage 如果你遇到的是不同源的 iframe 父子页面需要通信,postMessage 是唯一安全可靠的标准方案

  5. 实时通信用 WebSocket: 如果你的应用核心需求是实时双向数据交换(如聊天、协作),并且需要跨域,直接使用 WebSocket,它本身支持跨域。

  6. 避免 JSONP 和 document.domain 除非维护无法升级的极其老旧的系统,否则不要使用 JSONP 和 document.domain。它们存在严重的安全缺陷和兼容性问题。

⚠️ 安全注意事项

  • CORS:

    • 避免在生产环境中使用 Access-Control-Allow-Origin: *,特别是当接口涉及敏感数据或需要认证时。明确指定允许的域名。

    • 谨慎使用 Access-Control-Allow-Credentials: true,确保与之配合的 Access-Control-Allow-Origin 不是 *,并且客户端确实需要携带凭证(Cookie)。

  • 代理服务器:

    • 确保代理服务器本身安全,防止被滥用。

    • 在代理层做好认证授权,避免将未授权的请求转发给后端。

  • postMessage:

    • 始终在 message 事件监听器中验证 event.origin 这是防止恶意网站向你的页面发送消息的关键防线。

  • JSONP:

    • 绝对不要用于涉及敏感数据的接口。

    • 对回调函数名进行严格过滤,防止注入攻击。

  • document.domain:

    • 理解其安全风险,降低页面隔离性。

选择合适的跨域解决方案需要综合考虑你的具体需求(是 AJAX 请求还是 iframe 通信?)、对服务器的控制能力、安全性要求以及项目的技术栈。CORS 和代理服务器是解决 AJAX 跨域的两大支柱,postMessage 是解决 iframe 跨域的黄金标准。


评论