1. CORS (跨域资源共享) - 最主流、最推荐的方法

CORS全称是跨域资源共享(Cross-orgin resource sharing)

这是由 W3C 标准提供的官方方案,需要后端配合实现。后端服务器通过在 HTTP 响应头中设置特定的字段,来告诉浏览器允许某个源、方法或头进行跨域访问。

原理:
浏览器将CORS请求分为两类:简单请求非简单请求。对于非简单请求(如Content-Type为application/json或使用了自定义头),浏览器会先发送一个OPTIONS方法的“预检请求”(Preflight Request),预检通过后才会发送真正的请求。

后端设置示例 (Node.js/Express):

const express = require('express');
const app = express();

// 中间件:设置CORS响应头
app.use((req, res, next) => {
  // 允许请求的来源(* 表示允许任何来源,但出于安全考虑,生产环境应指定具体域名)
  res.header('Access-Control-Allow-Origin', 'http://localhost:8080'); 
  // 允许客户端携带的请求头(如 Authorization, Content-Type)
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
  // 允许的HTTP方法
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  // 允许浏览器在跨域请求中携带Cookie(Access-Control-Allow-Origin 不能为 *,需指定具体域名)
  res.header('Access-Control-Allow-Credentials', 'true');

  // 如果是OPTIONS请求(预检请求),直接返回200
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  next();
});

// 你的API路由
app.get('/api/data', (req, res) => {
  res.json({ message: 'This is data from CORS-enabled API!' });
});

app.listen(3000);

前端无需特殊处理,正常使用fetchaxios等发送请求即可。但如果设置了Credentials,前端也需要对应配置:

// 使用 axios
axios.get('http://api.other-site.com/data', {
  withCredentials: true // 告诉浏览器在跨域请求时携带cookie等凭证信息
});

// 使用 fetch
fetch('http://api.other-site.com/data', {
  credentials: 'include' // 等同于 withCredentials: true
});

CORS 简单请求 vs. 非简单请求

特性 简单请求 (Simple Request) 非简单请求 (Not-Simple Request)
触发条件 必须同时满足所有以下条件:
1. 方法为 GET, POST, 或 HEAD
2. Content-Type 仅为:text/plain, multipart/form-data, application/x-www-form-urlencoded
3. 未使用自定义头部
不满足上述任一条件的请求即为非简单请求
常见情况:
- 方法为 PUT, DELETE, PATCH
- Content-Typeapplication/json
- 使用了自定义头部 (如 Authorization, X-Custom-Header)
核心机制 直接请求 预检请求 (Preflight Request)
通信过程 1 次 HTTP 请求 2 次 HTTP 请求
1. OPTIONS 预检请求
2. 真实的请求
浏览器行为 自动在请求头中添加 Origin 字段,然后直接发出请求 1. 先自动发起 OPTIONS 预检请求询问服务器
2. 获得批准后,再发送真实请求
关键请求头 Origin 预检请求 (OPTIONS) 头:
- Origin
- Access-Control-Request-Method
- Access-Control-Request-Headers (如果使用了自定义头)
服务器关键响应头 - Access-Control-Allow-Origin (必需)
- Access-Control-Allow-Credentials (可选)
- Access-Control-Expose-Headers (可选)
预检响应头:
- Access-Control-Allow-Origin (必需)
- Access-Control-Allow-Methods (必需)
- Access-Control-Allow-Headers (可选)
- Access-Control-Max-Age (可选)
- Access-Control-Allow-Credentials (可选)

真实请求响应头与简单请求相同
主要目的 处理安全的、传统的跨域请求 处理可能有副作用的、更复杂的跨域请求,为服务器提供一层额外的安全控制
常见示例 GET /api/data
POST /api/submit (表单格式)
PUT /api/update/1
DELETE /api/delete/1
POST /api/login (JSON格式,带Authorization头)

补充说明:

  • 预检请求:这是浏览器的一种安全机制,旨在防止对服务器产生未预期的副作用(例如,一个来自恶意网站的 DELETE 请求)。服务器通过响应预检请求,明确告知浏览器它允许哪些方法、头部和源。
  • Access-Control-Max-Age:该头用于指定预检请求结果的缓存时间(单位:秒)。在有效期内,同一请求无需再次发起预检,从而提升性能。
  • 凭证请求:如果请求需要携带 Cookies 或 HTTP 认证信息,则服务器必须设置 Access-Control-Allow-Credentials: true,并且 Access-Control-Allow-Origin 不能为通配符 *,必须是具体的源。这对简单和非简单请求都适用。

2. 反向代理 (Reverse Proxy) - 开发阶段最常用、最安全的方法

在开发环境中,前端和服务端API之间存在跨域问题。我们可以在前端开发服务器和服务端API之间设置一个代理。前端将所有请求发送到同源的开发服务器,再由开发服务器代为转发给真正的API服务器。因为服务器之间通信没有同源策略限制,从而解决了跨域问题。

实现方式:

  • Vite:vite.config.js 中配置 server.proxy
  • Webpack (Vue CLI):vue.config.js 中配置 devServer.proxy

Vite 配置示例 (vite.config.js):

import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    port: 8080, // 前端开发服务器端口
    proxy: {
      // 代理所有以 '/api' 开头的请求
      '/api': {
        target: 'http://api.true-server.com:3000', // 真实的后端API地址
        changeOrigin: true, // 改变请求头中的Origin为目标地址,虚拟一个同源身份
        rewrite: (path) => path.replace(/^\/api/, '') // (可选)重写请求路径,去掉/api前缀
      }
    }
  }
})

前端代码调用示例:

// 前端现在请求的是同源的 /api/data,而不是 http://api.true-server.com:3000/data
axios.get('/api/data').then(response => {
  console.log(response.data);
});

3. JSONP (JSON with Padding) - 古老的方法,仅限GET请求

利用 <script> 标签没有跨域限制的特点,通过动态创建<script>标签,指定一个回调函数名作为参数传递给服务器,服务器返回一段以该回调函数包裹JSON数据的JS代码,前端在收到后立即执行。

例子:
前端代码:

function handleJSONPResponse(data) {
  // 这里是回调函数,处理从服务器返回的数据
  console.log('Received data:', data);
}

// 动态创建script标签
const script = document.createElement('script');
const url = 'http://api.other-site.com/data?callback=handleJSONPResponse';
script.src = url;
document.body.appendChild(script);

// 请求完成后移除script标签
script.onload = function() {
  document.body.removeChild(script);
};

后端响应:
服务器接收到请求,发现参数中有 callback=handleJSONPResponse,于是返回如下内容(不是JSON,而是一段可执行的JavaScript代码):

handleJSONPResponse({ "message": "This is data from JSONP!" });

缺点: 只支持GET方法,安全性较差,错误处理困难。


4. WebSocket - 双向通信协议,天然跨域

WebSocket是一种双向通信协议,在建立连接的HTTP握手阶段不受同源策略限制(但浏览器可能会在发起WebSocket连接时携带Origin头,服务器可以根据此决定是否接受连接)。

例子:
前端代码:

// 直接在客户端创建WebSocket连接,即使域名不同
const socket = new WebSocket('ws://api.other-site.com/ws');

socket.onopen = function(event) {
  console.log('Connection established!');
  socket.send('Hello Server!'); // 发送数据
};

socket.onmessage = function(event) {
  console.log('Message from server:', event.data); // 接收数据
};

socket.onerror = function(error) {
  console.error('WebSocket error:', error);
};

服务器端 (Node.js with ws library):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws, request) {
  // 这里可以检查 request.headers.origin 来决定是否允许连接
  console.log('Client connected');

  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
    // 广播消息给所有客户端
    wss.clients.forEach(function each(client) {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});

5. 修改 document.domain - 适用于二级域名相同的情况

该方法只适用于两个页面拥有相同基础域名(例如 a.example.comb.example.com),且协议和端口都相同的情况。通过将两个页面的 document.domain 都设置为相同的基础域名(example.com),浏览器就会认为它们同源,允许相互操作。

例子:
假设有两个页面:

  1. http://shop.example.com/page.html
  2. http://admin.example.com/other.html

在两个页面中均执行以下代码:

// 在两个页面的脚本中都设置
document.domain = 'example.com';

设置之后,这两个页面就可以相互访问对方的DOM了(例如使用window.parent进行iframe通信)。

缺点: 局限性非常大,只能用于特定场景,且一个页面一旦设置了document.domain,就无法再改回更具体的子域名。现代开发中已很少使用


总结与选择

方法 优点 缺点 适用场景
CORS W3C标准,安全可控,支持所有HTTP方法 需要后端配合修改服务器配置 生产环境的终极解决方案
反向代理 前端无需修改代码,开发体验好,安全 主要用于开发环境,生产环境需Nginx等配合 开发阶段的首选方案
JSONP 兼容老浏览器,无需后端特殊配置(需支持JSONP) 仅支持GET,安全性差,错误处理难 临时解决老旧系统的GET请求跨域
WebSocket 双向通信,实时性强,协议本身支持跨域 并非为普通API请求设计,协议较复杂 需要实时双向数据交换的场景(如聊天室、实时通知)
document.domain 可解决特定域名下的跨域问题 局限性极大(同基础域名、同协议、同端口) 传统且域名规划有继承关系的旧项目

最佳实践:

  • 开发阶段:使用 反向代理 (Vite/Webpack Proxy)。
  • 生产环境:让后端配置 CORS。如果架构复杂,也可以在Nginx等网关层统一配置反向代理。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐