引言
前端开发中最让人头疼的报错之一:
Access to XMLHttpRequest at 'http://api.example.com/user'
from origin 'http://www.example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
这就是跨域问题 。很多开发者的第一反应是"后端加个 Access-Control-Allow-Origin: * 就好了",但为什么加这个就能解决?什么时候该用 *?什么时候不该用?带 Cookie 的跨域请求怎么处理?
本文将彻底讲透 CORS 的原理、分类和解决方案。

第一部分:同源策略
一、什么是同源
两个 URL 同源,必须满足三个条件完全相同:
| 条件 | URL1 | URL2 | 是否同源 |
|---|---|---|---|
| 协议 | http:// |
https:// |
❌ 协议不同 |
| 域名 | www.a.com |
api.a.com |
❌ 子域名不同 |
| 端口 | :80 |
:8080 |
❌ 端口不同 |
| 全部相同 | http://a.com:80/a |
http://a.com:80/b |
✅ 同源 |
同源策略限制的是:
-
Cookie、LocalStorage 的读取
-
DOM 的访问
-
AJAX 请求的发送
不受同源策略限制的:
-
<script src="...">加载 JS -
<img src="...">加载图片 -
<link href="...">加载 CSS -
<a>链接跳转 -
表单提交
这就是为什么 JSONP 能跨域------它利用了 <script> 标签不受同源策略限制的"漏洞"。
第二部分:CORS 的分类
浏览器把跨域请求分为两类:简单请求 和非简单请求。

一、简单请求的三个条件(必须同时满足)
1. 请求方法是以下三种之一:
-
GET
-
HEAD
-
POST
2. 只包含以下请求头(不能有自定义头):
-
Accept
-
Accept-Language
-
Content-Language
-
Content-Type(仅限三种值)
3. Content-Type 只能是:
-
text/plain -
multipart/form-data -
application/x-www-form-urlencoded
任何不满足以上条件的就是非简单请求。比如:
-
PUT、DELETE、PATCH 方法
-
自定义请求头(如
Authorization、X-Custom-Header) -
Content-Type 是
application/json
二、简单请求的流程

核心头:
请求头:Origin: http://www.example.com
响应头:Access-Control-Allow-Origin: http://www.example.com
三、非简单请求(预检请求)

预检请求相关的头:
| 头(请求) | 含义 |
|---|---|
Access-Control-Request-Method |
告知真实请求的方法 |
Access-Control-Request-Headers |
告知真实请求的自定义头 |
| 头(响应) | 含义 |
|---|---|
Access-Control-Allow-Origin |
允许的源 |
Access-Control-Allow-Methods |
允许的方法 |
Access-Control-Allow-Headers |
允许的请求头 |
Access-Control-Max-Age |
预检缓存时间(秒),时间内不发第二次预检 |
第三部分:带 Cookie 的跨域请求
默认情况下,跨域请求不携带 Cookie。
一、前端设置
javascript
// 原生 XHR
const xhr = new XMLHttpRequest();
xhr.withCredentials = true; // 关键!
// Fetch
fetch('http://api.example.com/user', {
credentials: 'include' // 关键!
});
二、后端设置
javascript
响应头:
Access-Control-Allow-Origin: http://www.example.com ← 不能用 *!
Access-Control-Allow-Credentials: true ← 必须
两条硬规则:
-
Access-Control-Allow-Origin不能是*,必须是具体的源 -
必须加
Access-Control-Allow-Credentials: true

第四部分:完整 CORS 响应头速查
| 响应头 | 作用 | 示例 |
|---|---|---|
Access-Control-Allow-Origin |
允许的源 | http://www.example.com 或 * |
Access-Control-Allow-Credentials |
允许携带 Cookie | true |
Access-Control-Allow-Methods |
允许的 HTTP 方法 | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers |
允许的请求头 | Authorization, Content-Type |
Access-Control-Expose-Headers |
允许 JS 读取的响应头 | X-Total-Count |
Access-Control-Max-Age |
预检缓存时间 | 86400(24小时) |
第五部分:常见跨域解决方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| CORS | 服务器加响应头 | 标准方案,功能完整 | 需要后端配合 |
| JSONP | <script> 标签不受跨域限制 |
兼容老浏览器 | 只支持 GET |
| 反向代理 | 同域下转发请求 | 彻底避开跨域 | 需要运维配置 |
| postMessage | HTML5 跨文档通信 | iframe 间通信 | 需双方配合 |
| WebSocket | 不受同源策略限制 | 双向通信 | 需要服务器支持 |
JSONP 示例
html
<script>
function handleResponse(data) {
console.log(data);
}
</script>
<script src="http://api.example.com/user?callback=handleResponse"></script>
javascript
// 服务器返回
handleResponse({"name": "张三", "age": 20});
JSONP 已过时,新项目统一用 CORS。
反向代理(开发环境常用)
javascript
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true
}
}
}
};
前端请求 /api/user → 开发服务器转发到 http://api.example.com/api/user → 同源,无跨域问题。
第六部分:面试题
1. Q:什么是同源策略?
A:浏览器安全机制。两个 URL 的协议、域名、端口必须完全相同才算同源。同源策略限制不同源之间的 Cookie 读取、DOM 访问、AJAX 请求。
2. Q:简单请求和非简单请求的区别?
A:简单请求(GET/POST/HEAD + 特定 Content-Type)直接发送,浏览器自动带 Origin 头。非简单请求(PUT/DELETE、自定义头、application/json)先发 OPTIONS 预检请求,服务器允许后才发真实请求。
3. Q:跨域带 Cookie 需要什么配置?
A:前端设置 withCredentials: true;后端设置 Access-Control-Allow-Origin 为具体源(不能用 *)+ Access-Control-Allow-Credentials: true;Cookie 本身需要 SameSite=None; Secure。
4. Q:为什么跨域请求的响应被拦截了,但请求实际已发出?
A:浏览器的同源策略只拦截 JS 读取响应,不拦截请求的发送。服务器确实收到了请求并处理了。这是防止恶意网站读取用户数据,但不阻止数据提交。
5. Q:CORS 和 JSONP 选哪个?
A:CORS。JSONP 只支持 GET、有 XSS 风险、已过时。新项目统一用 CORS,需要兼容老 IE 时才考虑 JSONP。
总结
一、CORS 核心流程

二、关键响应头
javascript
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
三、一句话记忆
同源策略限制跨域 AJAX,CORS 通过服务器返回 Access-Control-Allow-Origin 等响应头来授权跨域访问。简单请求直接带 Origin,非简单请求先发 OPTIONS 预检。带 Cookie 需要前端开 withCredentials + 后端指定具体 Origin 并开 Credentials。