Skip to content

跨域问题详解

一、什么是跨域问题?

跨域(Cross-Origin) 是指浏览器在发起网络请求时,请求的目标地址与当前页面的地址在以下任意一个维度上存在差异:

维度示例(当前页面)示例(请求目标)是否跨域
协议不同http://example.comhttps://example.com✅ 跨域
域名不同http://a.comhttp://b.com✅ 跨域
子域名不同http://www.example.comhttp://api.example.com✅ 跨域
端口不同http://example.com:3000http://example.com:8080✅ 跨域
完全相同http://example.com:3000http://example.com:3000❌ 同源

同源的定义:协议 + 域名 + 端口 三者完全一致,才算同源。


二、为什么会有跨域问题?

2.1 同源策略(Same-Origin Policy)

跨域问题的根源是浏览器的 同源策略,这是浏览器最核心的安全机制之一,由 Netscape 在 1995 年提出。

同源策略的核心限制:

  • 无法读取非同源页面的 Cookie、LocalStorage、IndexedDB
  • 无法操作非同源页面的 DOM
  • 无法向非同源地址发送 AJAX 请求(请求会被浏览器拦截)

2.2 为什么需要同源策略?

试想没有同源策略的世界:

1. 用户登录了 bank.com,浏览器保存了登录 Cookie
2. 用户访问了恶意网站 evil.com
3. evil.com 的脚本直接向 bank.com 发送请求,并携带用户的 Cookie
4. 恶意脚本成功获取用户的银行账户信息!

同源策略正是为了防止这类 CSRF(跨站请求伪造)XSS(跨站脚本攻击) 而存在的。

2.3 注意:跨域是浏览器行为

⚠️ 重要:跨域限制只存在于浏览器环境中。服务器之间的请求、Postman、curl 等工具不受同源策略限制。


三、如何解决跨域问题?

方案一:CORS(跨域资源共享)— 推荐方案

CORS(Cross-Origin Resource Sharing) 是目前最标准、最主流的跨域解决方案,由服务端设置响应头来授权跨域访问。

简单请求 vs 预检请求

简单请求(满足以下所有条件):

  • 请求方法为 GETPOSTHEAD
  • Content-Type 为 text/plainmultipart/form-dataapplication/x-www-form-urlencoded

浏览器直接发送请求,并在请求头中带上 Origin 字段。

预检请求(Preflight)(不满足简单请求条件):

  • 浏览器先发送一个 OPTIONS 请求询问服务器是否允许
  • 服务器确认后,再发送真正的请求

服务端配置示例

Node.js / Express:

javascript
const cors = require('cors');

app.use(cors({
  origin: 'https://your-frontend.com',  // 允许的源
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true  // 允许携带 Cookie
}));

Nginx:

nginx
location /api/ {
    add_header Access-Control-Allow-Origin  "https://your-frontend.com";
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
    add_header Access-Control-Allow-Headers "Authorization, Content-Type";
    add_header Access-Control-Allow-Credentials "true";

    if ($request_method = OPTIONS) {
        return 204;
    }
}

Spring Boot(Java):

java
@CrossOrigin(origins = "https://your-frontend.com")
@RestController
public class ApiController {
    // ...
}

关键响应头说明

响应头说明
Access-Control-Allow-Origin允许的源,* 表示所有源(不能与 credentials 同时使用)
Access-Control-Allow-Methods允许的请求方法
Access-Control-Allow-Headers允许的请求头
Access-Control-Allow-Credentials是否允许携带 Cookie
Access-Control-Max-Age预检请求的缓存时间(秒)

方案二:代理服务器(Proxy)

通过在同源的服务器上设置代理,将请求转发到目标服务器,绕过浏览器的同源限制。

浏览器 → 同源代理服务器 → 目标服务器
         (无跨域限制)

开发环境:Vite 配置代理

javascript
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://backend-server.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
}

开发环境:webpack-dev-server 配置代理

javascript
// webpack.config.js
devServer: {
  proxy: {
    '/api': {
      target: 'http://backend-server.com',
      changeOrigin: true
    }
  }
}

✅ 代理方案在开发环境中非常常用,生产环境可使用 Nginx 反向代理。


方案三:JSONP(历史方案,了解即可)

利用 <script> 标签不受同源策略限制的特性,通过动态创建 script 标签来获取数据。

javascript
function handleData(data) {
  console.log('收到数据:', data);
}

const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleData';
document.body.appendChild(script);

缺点:

  • 只支持 GET 请求
  • 存在 XSS 安全风险
  • 需要服务端配合返回特定格式

❌ 现代开发中已基本被 CORS 取代,不推荐使用。


方案四:postMessage(跨窗口通信)

适用于 iframe 与父页面多个窗口/标签页之间的跨域通信场景。

javascript
// 发送方(页面 A)
const iframe = document.getElementById('my-iframe');
iframe.contentWindow.postMessage('Hello from A', 'https://page-b.com');

// 接收方(页面 B)
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://page-a.com') return; // 安全验证
  console.log('收到消息:', event.data);
});

方案五:WebSocket

WebSocket 协议不受同源策略限制,可以跨域建立连接(服务端可验证 Origin 头)。

javascript
const ws = new WebSocket('wss://api.other-domain.com/socket');

ws.onopen = () => ws.send('Hello');
ws.onmessage = (e) => console.log(e.data);

四、各方案对比

方案适用场景难度安全性推荐度
CORS所有 HTTP 请求⭐⭐⭐⭐⭐⭐⭐⭐⭐
代理服务器开发/生产环境⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
JSONP仅 GET 请求(旧系统)⭐⭐⭐⭐
postMessage跨窗口/iframe 通信⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
WebSocket实时双向通信⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

五、常见问题与排查

Q1:设置了 Access-Control-Allow-Origin: *,但还是跨域报错?

原因:当请求需要携带 Cookie(credentials: 'include')时,* 无效,必须指定具体的源。

javascript
// ❌ 错误
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

// ✅ 正确
Access-Control-Allow-Origin: https://your-frontend.com
Access-Control-Allow-Credentials: true

Q2:OPTIONS 预检请求返回 404 或 405?

服务端没有处理 OPTIONS 请求方法,需要添加对应的路由或在 Nginx 中配置返回 204。

Q3:开发环境正常,生产环境跨域?

通常是生产环境的 Nginx/Apache 没有配置 CORS 响应头,或者配置的域名与实际前端域名不一致。

需要同时满足三个条件:

  1. 前端请求设置 credentials: 'include'
  2. 服务端响应头包含 Access-Control-Allow-Credentials: true
  3. 服务端 Access-Control-Allow-Origin 必须是具体域名,不能是 *

六、安全建议

  • ✅ 生产环境中 Access-Control-Allow-Origin 应指定具体域名,避免使用 *
  • ✅ 对 Origin 进行白名单验证,而不是直接将请求中的 Origin 原样返回
  • ✅ 合理设置 Access-Control-Max-Age,减少预检请求次数(建议 600~86400 秒)
  • ✅ 最小化 Access-Control-Allow-MethodsAccess-Control-Allow-Headers,按需开放
  • ❌ 不要在开发图省事的情况下,将生产环境配置为 Allow-Origin: *

七、总结

跨域问题
  ├── 根源:浏览器同源策略(安全机制)
  ├── 触发条件:协议 / 域名 / 端口 任一不同
  └── 解决方案
        ├── CORS(主流,服务端配置响应头)
        ├── 代理服务器(开发/生产环境均可用)
        ├── JSONP(已过时,仅 GET)
        ├── postMessage(跨窗口通信专用)
        └── WebSocket(实时通信场景)

💡 推荐实践:生产环境使用 CORS + Nginx 反向代理 的组合方案,开发环境使用脚手架自带的 Proxy 代理,简洁安全。

如有转载或 CV 的请标注本站原文地址

访客数 --| 总访问量 --