一、同源策略
要理解跨域,先要理解"同源策略"。这是浏览器最核心的安全机制之一:浏览器不允许网页发起跨源的 HTTP 请求(更准确地说是允许发起,但不允许读取响应)。
什么是"同源"?两个 URL 满足以下三个条件都相同,才叫同源:
- 协议相同(http vs https)
- 域名相同(a.com vs b.com)
- 端口相同(80 vs 8080)
任意一个不同,就是跨域。
举几个例子:
| 当前页面 URL | 请求 URL | 是否跨域 | 原因 |
| http://a.com/page | http://a.com/api | 否 | 协议、域名、端口都同 |
| http://a.com/page | https://a.com/api | 是 | 协议不同 |
| http://a.com:80/page | http://a.com:8080/api | 是 | 端口不同 |
| http://a.com/page | http://b.com/api | 是 | 域名不同 |
| http://a.com/page | http://api.a.com/data | 是 | 子域不同 |
注意:a.com 和 api.a.com 是跨域的,即使它们是父子域关系。
二、为什么要有同源策略
假设没有同源策略,会发生什么?
你登录了银行 bank.com,浏览器存了 bank.com 的 cookie。然后你打开一个恶意网站 evil.com,这个网站的 JavaScript 可以向 bank.com 发请求——由于浏览器会自动带上 cookie,bank.com 会以为是你本人在操作,返回你的账户信息。evil.com 拿到数据后就能干坏事。
同源策略就是防止这种"一个网站偷读另一个网站数据"的情况。它规定:A 网站的 JavaScript 不能读取 B 网站返回的数据,即使请求发出去了,响应也被浏览器拦截。
三、CORS 是什么
CORS(Cross-Origin Resource Sharing,跨源资源共享)是 W3C 标准,它是在同源策略之上开的一个"白名单口子"。原理是:如果 B 网站的服务器在响应头里明确表示"我允许 A 网站访问",浏览器就放行。
关键点:CORS 是浏览器和服务器的协作,主要由服务器配置。浏览器负责检查响应头,决定是否把数据交给 JavaScript。
四、简单请求 vs 预检请求
CORS 把请求分成两类:简单请求和预检请求。
简单请求
满足以下所有条件的请求是"简单请求":
| 条件 | 允许的值 |
| 方法 | GET、HEAD、POST |
| 自定义头 | 只能是几个安全头(Accept、Content-Language 等) |
| Content-Type | 只能是三种:text/plain、multipart/form-data、application/x-www-form-urlencoded |
简单请求直接发出去,服务器在响应头里加上 CORS 头,浏览器检查通过就放行。
预检请求
不满足简单请求条件的(比如用了 PUT/DELETE、发了 application/json、加了自定义头),浏览器会先发一个 OPTIONS 请求问服务器:"我能不能用这个方法、这些头来访问?"。这个 OPTIONS 请求就是预检请求(Preflight Request)。
预检请求流程:
- 浏览器发
OPTIONS /api/users,带上这些头:
Origin: http://a.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
- 服务器响应:
Access-Control-Allow-Origin: http://a.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
- 浏览器检查通过,再发真正的 PUT 请求
- 如果检查不通过,真正的请求不会发出,直接报错
对比表
| 维度 | 简单请求 | 预检请求 |
| 请求次数 | 1 次 | 2 次(OPTIONS + 实际请求) |
| 触发条件 | 满足上面所有条件 | 不满足简单请求条件 |
| 是否能优化 | - | 可用 Max-Age 缓存预检 |
实际开发中,绝大多数 API 请求都是预检请求——因为现代前端都用 application/json 发数据,这本身就触发了预检。
五、关键响应头
CORS 相关的响应头有这些:
| 响应头 | 作用 |
| Access-Control-Allow-Origin | 允许哪些源访问(必填) |
| Access-Control-Allow-Methods | 允许哪些方法 |
| Access-Control-Allow-Headers | 允许哪些请求头 |
| Access-Control-Allow-Credentials | 是否允许带 cookie |
| Access-Control-Expose-Headers | 允许 JS 读哪些响应头 |
| Access-Control-Max-Age | 预检结果缓存时间(秒) |
最关键的是 Access-Control-Allow-Origin,它有几个常见写法:
# 允许所有源(不安全,不能配合 cookie)
Access-Control-Allow-Origin: *
# 允许单个源
Access-Control-Allow-Origin: https://a.com
# 动态返回请求方的 Origin(推荐)
Access-Control-Allow-Origin: http://a.com
六、带 Cookie 的跨域
默认情况下,跨域请求不会带 cookie。如果需要带(比如登录态跨域),需要:
- 前端:
fetch(url, { credentials: "include" })或axios配withCredentials: true - 后端:响应头加
Access-Control-Allow-Credentials: true - 后端:
Access-Control-Allow-Origin不能是*,必须是具体域名
三个条件缺一不可。这是常见踩坑点——很多人配置了前两个,但 Allow-Origin: * 没改,浏览器仍然拒绝。
七、常见跨域解决方案
除了 CORS,历史上还有几种跨域方案:
1. CORS(推荐)
服务器配置响应头,浏览器自动处理。这是现代标准方案,绝大多数场景都应该用它。
2. JSONP
利用 标签不受同源策略限制的特点,让服务器返回一段 JS 代码调用回调函数。
<script src="http://b.com/api?callback=handleData"></script>
<script>
function handleData(data) {
console.log(data);
}
</script>
只支持 GET,已经被 CORS 取代,但老系统里还常见。
3. 代理服务器
前端请求同源的开发服务器,开发服务器再转发到目标 API。开发环境常用。
Vue/Vite 配置示例:
// vite.config.js
export default {
server: {
proxy: {
"/api": {
target: "http://b.com",
changeOrigin: true
}
}
}
};
这样前端请求 /api/users,Vite 把它转发到 http://b.com/api/users,浏览器认为是同源,不触发 CORS。
4. Nginx 反向代理
生产环境常用的方式,和开发代理原理一样:
location /api/ {
proxy_pass http://backend.example.com/;
}
方案对比
| 方案 | 难度 | 安全性 | 灵活性 | 适用场景 |
| CORS | 低 | 高 | 高 | 现代 Web 应用 |
| JSONP | 低 | 低 | 低 | 老系统兼容 |
| 开发代理 | 低 | - | 中 | 仅开发环境 |
| Nginx 代理 | 中 | 高 | 高 | 生产环境 |
八、CORS 常见错误排查
错误 1:No 'Access-Control-Allow-Origin' header
服务器没返回 Access-Control-Allow-Origin 头。检查后端 CORS 配置。
错误 2:The value of 'Access-Control-Allow-Origin' must not be wildcard
带 cookie 时不能用 *,要改成具体域名。
错误 3:Preflight response is not successful
OPTIONS 请求失败。检查服务器是否正确处理 OPTIONS 方法,以及 Nginx 是否拦截了 OPTIONS。
错误 4:CORS policy: Method not allowed
服务器没在 Access-Control-Allow-Methods 里允许当前方法。
九、实践推荐
调试跨域问题时,最快的方法是直接用在线工具发请求看响应头——能立刻判断是后端没配 CORS、还是配置有误:
- CORS 在线检测工具:https://52tool.net/tools/network/cors
输入请求 URL,自动模拟跨域请求,展示响应头、预检请求结果、是否允许 cookie 等,是排查"为什么我的接口跨域了"时的必备工具。