一、跨域问题的本质:同源策略与安全边界
1.1 什么是跨域?
跨域(Cross-Origin)的核心根源,是浏览器内置的同源策略,指的是浏览器出于安全考虑,限制一个源(Origin)的脚本访问另一个源的资源。这里的”源”由三个要素组成,所谓「同源」,要求两个 URL 的以下三个要素完全一致:
- 协议(http vs https)
- 域名(example.com vs api.example.com)
- 端口(80 vs 8080)
只要三者中任一不同,即构成跨域。举个直观的例子,以 http://www.example.com:80 为基准,同源判定结果如下:
| 请求 URL | 是否同源 | 跨域原因 |
|---|---|---|
https://www.example.com |
否 | 协议不同(http→https) |
http://api.example.com |
否 | 域名不同(主域一致,子域不同) |
http://www.example.com:3000 |
否 | 端口不同(80→3000) |
http://www.example.com/user |
是 | 仅路径不同,三要素完全一致 |
补充:
核心定义
- 主域(注册域名):是用户通过域名注册商合法申请、拥有完整所有权与管理权的最小独立域名单元,格式为「自定义主体 + 顶级域」,比如
example.com、example.com.cn。主域是域名所有权的核心载体,必须付费注册,可自主创建所有下级子域。- 子域:是主域持有者在主域基础上,免费自主配置的下级从属域名,无需额外注册。比如主域
example.com的子域包括www.example.com、api.example.com、admin.example.com等,子域完全依附于主域,可独立配置解析指向不同服务,但无独立所有权。
1.2 一个必须纠正的核心误区
跨域是浏览器的单向限制,不是服务器拒绝了你的请求
真实的请求流程是这样的:
- 前端发起跨域请求,浏览器正常将请求发送到目标服务器
- 服务器接收请求,正常处理并返回响应数据
- 浏览器收到响应后,检查响应头的 CORS 规则,判定是否允许当前源访问
- 若规则不匹配,浏览器直接拦截响应,抛出 CORS 报错,前端无法拿到任何响应数据
简单说:请求发出去了,服务器也正常返回了,只是浏览器把数据「扣下了」。这也是为什么你在 Network 面板能看到请求的状态码是 200,却拿不到响应体的核心原因。
1.3 同源策略到底限制了什么?
同源策略就像浏览器给每个页面加了一道「安全隔离墙」,主要限制三类行为:
- 数据存储访问:禁止读取 / 修改非同源页面的 Cookie、LocalStorage、IndexedDB 等存储数据
- DOM 操作:禁止获取非同源页面的 DOM 元素,防止恶意页面篡改嵌入的第三方页面
- 网络请求:限制 XMLHttpRequest、Fetch API 发起的跨域 AJAX 请求,这也是我们最常遇到的跨域场景
但有一个关键例外:HTML 的资源嵌入标签不受同源策略限制。
比如<script>、<img>、<link>、<iframe>等标签,可以正常加载跨域资源,这也是 JSONP、图片打点等方案的底层原理。
1.4 为什么会有同源策略?
同源策略是浏览器的核心安全机制,主要防范:
XSS 攻击:恶意脚本窃取用户数据
CSRF 攻击:伪造用户身份进行恶意操作
数据泄露:敏感信息被未授权访问
举两个最直观的风险场景:
没有同源策略,你打开的恶意钓鱼网站,可以直接读取你网银页面的 Cookie,轻松盗取你的账户信息,发起转账操作
没有同源策略,恶意页面可以嵌入你的电商支付页面,篡改 DOM 元素的支付金额,诱导你完成超额付款
同源策略的核心意义,就是隔离不同站点的资源,防止恶意网站窃取用户的敏感数据,从根源上阻断绝大多数 CSRF、XSS 衍生的攻击行为。
二、主流跨域解决方案
2.1 CORS:跨域资源共享的核心机制
2.1.1 CORS 工作原理
CORS(Cross-Origin Resource Sharing)是 W3C 标准,通过HTTP 响应头告知浏览器是否允许跨域访问。其核心流程分为两类:
简单请求(Simple Request)
满足以下条件的请求为简单请求:
- 方法仅限
GET、HEAD、POST Content-Type为application/x-www-form-urlencoded、multipart/form-data、text/plain- 无自定义请求头
简单请求直接发送,无需预检。
非简单请求(Preflight Request)
复杂请求会触发浏览器的预检机制:
浏览器先使用
OPTIONS方法发起一个「预检请求」,携带三个核心请求头:Origin:请求的来源域Access-Control-Request-Method:正式请求要使用的方法Access-Control-Request-Headers:正式请求要携带的自定义头
1
2
3
4
5// 预检请求示例
OPTIONS /api/data HTTP/1.1
Origin: https://www.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization, Content-Type服务器接收预检请求,校验后返回对应的 CORS 响应头,明确是否允许该请求
浏览器校验预检响应通过后,才会发起正式的 HTTP 请求;校验失败则直接拦截,正式请求永远不会发出
2.1.2 核心 CORS 响应头
服务器的 CORS 配置,本质就是正确设置以下 HTTP 响应头,每一个都有明确的作用,不可乱配:
| 响应头 | 必需性 | 核心作用 | 安全注意事项 |
|---|---|---|---|
Access-Control-Allow-Origin |
必需 | 指定允许访问的源,值可以是单个具体源、null,或通配符* |
非公开 API 严禁使用*;携带凭据时,必须为具体源,绝对不能用* |
Access-Control-Allow-Methods |
预检必需 | 指定正式请求允许的 HTTP 方法,多个用逗号分隔 | 按需开放,不要全量开放GET,POST,PUT,DELETE,PATCH |
Access-Control-Allow-Headers |
预检必需 | 指定正式请求允许携带的自定义请求头 | 按需开放,比如仅开放Content-Type,Authorization |
Access-Control-Allow-Credentials |
可选 | 指定是否允许请求携带 Cookie、HTTP 认证等身份凭证,值只能是true |
开启后,Allow-Origin不能为*,前端必须设置withCredentials: true |
Access-Control-Max-Age |
可选 | 指定预检请求的缓存时间(秒),缓存期内不会重复发起预检 | 建议设置合理值(如 86400 秒 = 1 天),减少 OPTIONS 请求开销 |
1 | // 使用示例 |
2.2 服务端 CORS 配置(生产环境首选)
Node.js/Express 方案
1 | const express = require('express'); |
Spring Boot 4.0 方案
1 |
|
FastAPI 方案
1 | from fastapi import FastAPI |
2.2 代理方案
代理方案的核心原理,直击同源策略的本质:同源策略仅限制浏览器与服务器之间的通信,服务器与服务器之间的 HTTP 通信,没有任何跨域限制。
我们只需要搭建一个「代理中转服务器」,让浏览器把请求发给同源的代理服务器,再由代理服务器转发给真实的后端接口,就能彻底绕开跨域问题 —— 对浏览器来说,请求是发给同源的代理服务,不存在跨域;对后端来说,请求来自正常的服务器,无需做任何 CORS 配置。其中,代理方案分为开发环境和生产环境两类。
开发环境代理
Vite/Vue3 配置
1 | // vite.config.js |
Webpack DevServer 配置
1 | // webpack.config.js |
配置完成后,前端只需把请求地址写成
/api/xxx,开发服务器会自动把请求转发到http://localhost:3000/xxx,浏览器全程认为是同源请求,不会有任何跨域问题。
Nginx 反向代理(生产环境推荐)
生产环境中,我们通常使用 Nginx 作为静态资源服务器和反向代理服务器,实现和开发代理完全一致的效果,同时还能兼顾负载均衡、静态资源缓存等能力。
1 | server { |
配置完成后,用户访问
https://www.example.com加载前端页面,所有/api开头的请求都会被 Nginx 转发到后端服务,前端和接口处于完全同源的状态,从根源上彻底消除了跨域问题,无需后端做任何 CORS 配置。
2.3 JSONP:仅兼容老旧浏览器的古董方案
JSONP 是早期前端解决跨域的方案,核心原理是利用<script>标签不受同源策略限制的特性,通过动态创建 script 标签,请求后端返回一个「函数调用」,把数据作为参数传入,前端在全局定义该函数,从而拿到跨域数据。
2.4 postMessage:跨窗口 /iframe 通信专属方案
postMessage 是 HTML5 提供的 API,专门用于解决「不同源的页面之间」的通信问题,最常见的场景:主页面与嵌入的跨域 iframe 通信、同一浏览器打开的多个跨域标签页通信。
2.5 WebSocket:全双工通信无跨域限制
WebSocket 协议本身不受浏览器同源策略限制,只要服务器支持,前端可以和任意源的 WebSocket 服务建立连接,实现全双工通信,是实时通信场景的最优解。
2.3 跨域解决方案对比与选型建议
| 方案 | 适用场景 | 优点 | 缺点 | 安全性 |
|---|---|---|---|---|
| 服务端 CORS | 生产环境 | 标准化、浏览器原生支持 | 需要后端配合 | ★★★★☆ |
| Nginx 代理 | 生产环境 | 性能好、配置灵活 | 运维复杂度高 | ★★★★★ |
| 开发代理 | 开发环境 | 无需后端改动、调试方便 | 仅限开发环境 | ★★★★☆ |
| JSONP | 传统项目 | 兼容性好 | 仅支持 GET、有安全风险 | ★☆☆☆☆ |
| WebSocket | 实时通信 | 无跨域限制 | 仅适用于特定场景 | ★★★★☆ |
选型建议
- 生产环境:优先选择 Nginx 反向代理或服务端 CORS 配置
- 开发环境:使用 Webpack/Vite 内置代理
- 遗留系统:考虑 WebSocket 或服务端代理
- 移动端/小程序:服务端统一代理,避免前端跨域问题