前端跨域解决方案(学习用)

跨域问题的根源是浏览器的同源策略(Same-Origin Policy) 。它是浏览器最核心的安全机制,限制了一个源下的文档或脚本与另一个源的资源交互。

  • 的定义:协议 + 域名 + 端口 三者完全一致。

  • 同源策略主要限制:

    • 不同源的 Cookie、LocalStorage、IndexedDB 无法互相访问。
    • 不同源的 DOM 无法通过 JS 直接获取(iframe 跨域隔离)。
    • 不同源的 AJAX 请求(XMLHttpRequestfetch)默认被拦截,无法读取响应内容。

有些标签可以跨域加载资源,但无法读取内容:

<script><img><link><video><audio><iframe> 等。

所有跨域方案都是在安全的前提下绕过同源策略限制


一、JSONP(JSON with Padding)

原理

利用 <script> 标签不受同源策略限制的特性,动态创建一个 script 标签,向服务端请求一段可执行的 JS 代码,代码中调用本地预先定义好的回调函数,并把数据作为参数传入。

前端实现

js 复制代码
function jsonp(url, callbackName) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    // 定义全局回调函数
    window[callbackName] = function(data) {
      resolve(data);
      document.body.removeChild(script);
      delete window[callbackName];
    };

    // 拼接 URL(服务端需返回类似 `callbackName({...})`)
    script.src = `${url}?callback=${callbackName}`;
    document.body.appendChild(script);
  });
}

// 调用
jsonp('https://api.example.com/data', 'handleData').then(data => console.log(data));

服务端处理(Node.js 示例)

js 复制代码
app.get('/data', (req, res) => {
  const callback = req.query.callback;
  const data = { user: 'Alice' };
  res.end(`${callback}(${JSON.stringify(data)})`);
});

优缺点

  • 优点:兼容极老的浏览器,不依赖现代浏览器特性。

  • 缺点

    • 只支持 GET 请求。
    • 错误处理困难(script 加载失败没有 HTTP 状态码,需通过 onerror 和定时器兜底)。
    • 安全风险:被调用方必须完全信任,可能执行恶意脚本。
    • 无法设置自定义请求头。

如今几乎已被 CORS 取代,但在某些遗留系统或第三方 JS SDK 中可能还有应用。


二、CORS(Cross-Origin Resource Sharing)

这是现代解决跨域的标准方案,由浏览器和服务器协作完成。核心是服务器通过 HTTP 响应头告知浏览器:"允许某个源访问我"。

两类请求

浏览器将跨域请求分为两类,行为不同。

1. 简单请求

同时满足以下条件:

  • 方法:GETHEADPOST 之一。
  • 头部只包含安全的:AcceptAccept-LanguageContent-LanguageContent-Type(且值必须是 text/plainmultipart/form-dataapplication/x-www-form-urlencoded 之一)。
  • 没有 ReadableStream 对象。

流程 :浏览器直接发请求,自动在请求头加上 Origin(当前网页的来源),服务器若允许则返回 Access-Control-Allow-Origin 头部,浏览器检查该头决定是否把响应交给前端 JS。

2. 预检请求(Preflight)

非简单请求(如 PUTDELETE、带自定义头 AuthorizationContent-Type: application/json)会先发一个 OPTIONS 方法探测,确认服务器允许后才发正式请求。

预检阶段头部

  • 请求头:OriginAccess-Control-Request-Method(正式请求的方法)、Access-Control-Request-Headers(正式请求的自定义头)。

  • 响应头:

    • Access-Control-Allow-Origin:允许的源,* 代表所有(但不能与 credentials 共存)。
    • Access-Control-Allow-Methods:允许的方法,如 GET, POST, PUT
    • Access-Control-Allow-Headers:允许的头部。
    • Access-Control-Max-Age:预检结果缓存时间(秒),期间不再发预检。

正式请求 与简单请求类似,也会带 Origin,服务器需同样返回 Access-Control-Allow-Origin

默认跨域请求不携带 Cookie,需前端设置 withCredentials(XHR)或 credentials: 'include'(fetch),同时服务器返回:

js 复制代码
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: 具体的源(不能为 *)

常见 CORS 配置示例(Express)

js 复制代码
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://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') {
    return res.sendStatus(204);
  }
  next();
});

优缺点

  • 优点:标准、安全、支持所有 HTTP 方法、精细控制。
  • 缺点:需要服务端配合;预检请求增加一次往返(但可缓存)。

三、代理转发(Proxy)

核心思想:同源策略只限制浏览器端,服务端没有此限制。通过配置代理服务器,把前端的请求转发到目标服务器,浏览器只与同源的代理通信。

1. 开发环境(Webpack DevServer / Vite)

js 复制代码
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,      // 修改请求头的 Origin 为目标地址
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
};

前端请求 /api/user,DevServer 将其转发到 https://api.example.com/user,绕过浏览器跨域限制。

2. 生产环境(Nginx 反向代理)

js 复制代码
location /api/ {
    proxy_pass https://api.example.com/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

所有指向当前域 /api/ 的请求被转发,对浏览器来说仍是同源。

优缺点

  • 优点:前端无侵入,可隐藏真实后端地址,支持复杂场景。
  • 缺点:增加了服务器维护成本;对 WebSocket 等也需额外配置。

四、WebSocket

WebSocket 协议不实行同源策略,但握手阶段会发送 Origin 头。服务器可以通过检查 Origin 来决定是否允许连接。

js 复制代码
// 前端
const ws = new WebSocket('wss://api.example.com/socket');
ws.onmessage = (e) => console.log(e.data);

// 服务端(Node.js ws 库)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
  const origin = req.headers.origin;
  // 可校验 origin 拒绝未知来源
  ws.send('connected');
});

适用场景

  • 实时通信,如聊天、协作编辑、行情推送。跨域通信只是其附带特性。

五、postMessage(跨文档消息传递)

用于不同窗口/iframe 之间的安全通信,即使不同源也可以传输数据。

发送方

js 复制代码
// 父页面
const iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage('Hello from parent', 'https://child.example.com');

接收方

js 复制代码
window.addEventListener('message', (event) => {
  // 始终验证来源!
  if (event.origin !== 'https://parent.example.com') return;
  console.log('收到:', event.data);
  // 可以回复
  event.source.postMessage('Hi back', event.origin);
});

关键安全措施

  • 发送时必须指定精确的 targetOrigin,不要用 *
  • 接收时务必检查 event.origin,防止恶意页面伪造消息。

应用

  • 页面与跨域 iframe 通信(如微前端、第三方支付窗口)。
  • 与 Web Worker 通信(专用 Worker 默认同源,Shared Worker 可能跨域)。

六、其他辅助方案(简要)

1. document.domain(已逐步废弃)

将两个不同子域的页面的 document.domain 设置为相同主域,可实现跨子域通信。但现代浏览器严格限制,许多 API 不再支持。

2. window.name

在一个窗口的生命周期里,window.name 属性保持不变,即使跳转跨域页面。结合 iframe 可实现跨域传递数据,但属于 hack 手法,已不推荐。

3. location.hash

通过修改 url 的 hash 和 onhashchange 事件在不同域之间传递数据,同样属于 hack。


七、各方案对比总结

方案 适用场景 安全性 是否需服务端 现代推荐度
CORS 同源 AJAX 调用 高(精细控制) 是(响应头) ⭐⭐⭐⭐⭐
代理 开发/生产环境通用 高(无浏览器限制) 需代理服务器 ⭐⭐⭐⭐⭐
JSONP 老旧浏览器兼容 低(易 XSS) 是(返回 JS)
WebSocket 实时双工通信 中(可校验 Origin) 需 WS 服务 ⭐⭐⭐⭐
postMessage 跨窗口/iframe 通信 高(需验证 origin) ⭐⭐⭐⭐⭐

实际开发中, "CORS + 代理" 是解决 95% 跨域问题的黄金组合;剩下 5% 可能会用 postMessageWebSocket。理解了每个方案的本质和限制,就能根据场景选择最合适的办法。

相关推荐
阡陌Jony1 小时前
关于前端路由中的参数问题的学习(二)
前端
Colin草率地做慢慢地改1 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构
IT_陈寒2 小时前
SpringBoot自动配置这个坑,我踩进去又爬出来了
前端·人工智能·后端
铁皮饭盒3 小时前
Bun 哪比 Node.js 快?
javascript·后端
JieE21211 小时前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
runnerdancer11 小时前
LLM是怎么处理messages数组的,提示词缓存又是什么
前端·agent
陈随易12 小时前
VSCode的Copilot扩展支持接入DeepSeek,Kimi了!
前端·后端·程序员
我不是外星人13 小时前
有了 Harness Engineering ,真的还需要研发工程师吗?
前端·后端·ai编程
candyTong13 小时前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构