CORS 跨域笔记
1. 同源策略
浏览器的同源策略要求下面三项全部相同才算同源。
示例: React 跑在 http://localhost:10086,API 在 http://localhost:8080
| 项 | 你的例子 |
|---|---|
| 协议 | 都是 http |
| 域名 | 都是 localhost |
| 端口 | 10086 ≠ 8080 → 跨域 |
所以 React(10086)请求 API(8080)属于跨域请求。
补充: 跨域 ≠ 一定报错。浏览器会发请求,但若响应里没有合适的 CORS 头,浏览器会拦截 JS 读取响应,控制台才报 CORS 错。Postman、curl 不是浏览器,不受 CORS 限制,所以 Postman 能通、前端
fetch报错,这是正常现象。
2. CORS 是什么
同源策略是浏览器的安全规则;CORS 是服务器通过响应头告诉浏览器「这个跨域请求我允许」的机制。
服务器要返回类似这样的头(开发环境常见写法):
Access-Control-Allow-Origin: http://localhost:10086
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
POST 带 Content-Type: application/json 时,浏览器还会先发一次 OPTIONS 预检请求,服务器也要正确响应,否则照样失败。
常用响应头说明
1. Access-Control-Allow-Origin
作用: 告诉浏览器「这个跨域响应允许被哪个页面读」。
-
*vs 具体域名: 带 Cookie /credentials: 'include'时不能用*,必须回显具体 origin。 -
安全: 不能无脑
origin || '*'反射任意 Origin(会被恶意站点利用);应做白名单校验。
javascript
const allowed = ['https://a.com', 'https://b.com'];
if (allowed.includes(origin)) {
res.set('Access-Control-Allow-Origin', origin);
}
2. Vary: Origin
作用: 告诉 CDN/缓存:响应内容随 Origin 请求头变化,不能对所有用户共用同一份缓存。
-
Origin: https://a.com→ 缓存的Allow-Origin: https://a.com -
Origin: https://b.com→ 应走另一份缓存(或缓存未命中再请求源站) -
不配的风险: A 站拿到的 CORS 头可能被缓存后给 B 站用,导致跨域失败或安全问题
提示: 只有在你动态设置
Allow-Origin(按 origin 回显)且前面有 CDN/代理缓存时,才强烈建议加Vary: Origin。不是每个项目都必配(无 CDN/无缓存可略)。
3. Access-Control-Allow-Credentials
核心用途就一类:前后端分离且域名不同,还要用浏览器 Cookie 做登录态。
作用: 允许跨域请求携带 Cookie、 TLS 客户端证书(很少见)、HTTP 基本认证(浏览器弹窗那种,很少见)等凭证。
-
前端必须配合:
fetch(..., { credentials: 'include' })或 axioswithCredentials: true -
互斥规则:
Allow-Origin: *+Allow-Credentials: true→ 浏览器直接拒绝 -
若你写的是具体 origin(如
https://a.com),可以和Credentials: true一起用
提示: 登录态跨域、Cookie 跨子域失败时,检查这一对配置。
4. Access-Control-Allow-Methods
作用: 预检 OPTIONS 时告诉浏览器允许哪些方法。
-
常见值:
GET, POST, PUT, DELETE -
非简单请求(如自定义头、PUT、
Content-Type: application/json)会先发 OPTIONS
提示: POST 带 JSON 会触发预检,OPTIONS 要返回这个头;纯 GET 简单请求往往不需要。
预检流程示例:
-
前端跨域 POST,并带
Authorization、Content-Type: application/json -
浏览器判断:非简单请求 → 先发 OPTIONS 预检
-
服务端 OPTIONS 响应要包含:
Access-Control-Allow-Headers: Content-Type, Authorization -
预检通过后,浏览器才发真正的 POST(带 Authorization)
为啥要 Content-Type?
-
Content-Type: application/json不属于「简单 Content-Type」 -
会触发预检,必须在
Allow-Headers里声明
为啥要 Authorization?
-
Authorization不是简单请求头 -
前端带 Token 时,预检必须声明允许
5. Access-Control-Allow-Headers
作用: 预检时声明客户端(浏览器)可以带哪些非简单请求头。
-
常见值:
Content-Type, Authorization, X-Requested-With等 -
前端加了自定义头(如
X-Token)却没写在这里 → 预检失败
提示: 出现
Request header field xxx is not allowed时补这个头。
6. Access-Control-Max-Age
作用: 浏览器缓存预检结果,减少重复 OPTIONS。
-
86400= 24 小时 -
开发环境可设小一点便于调试;生产可设大
提示: 少发预检、提升性能。
7. Access-Control-Expose-Headers
默认行为: JS 只能读少数「安全」响应头(如 Content-Type)。
作用: 让前端能读自定义头,如 Content-Disposition(下载文件名)、Content-Length。
- 典型场景: 文件下载、分页总数在自定义头里
提示: 前端
response.headers.get('Content-Disposition')为null时,要加这个头。
后端完整 CORS 中间件示例
javascript
app.use((req, res, next) => {
res.set({
"Access-Control-Allow-Origin": "http://192.168.88.119:10086/", // 允许哪个前端来源访问
"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS", // 允许哪些 HTTP 方法
"Access-Control-Allow-Headers": "Content-Type, Authorization", // 允许哪些请求头
"Access-Control-Allow-Credentials": "true", // 允许跨域带 Cookie / 凭证
"Access-Control-Max-Age": "86400", // 预检 OPTIONS 结果缓存 24 小时,少发预检
"Access-Control-Expose-Headers": "Content-Disposition, Content-Length", // 允许浏览器访问哪些响应头
});
if (req.method === "OPTIONS") {
return res.status(204).send();
}
next();
});
3. 本项目的实际地址
| 项 | 值 |
|---|---|
| 局域网 IP | http://192.168.88.119 |
| 后端端口 | 8080 |
| 接口路径示例 | /api/todos |
| 完整 API 地址 | http://192.168.88.119:8080/api/todos |
前后端端口号不一致,需要解决跨域。有两种常见方案:
4. 方案一:后端配置跨域头
javascript
const allowedOrigins = [
// ✅ 生产写法:按环境变量白名单来
// "https://你的前端域名.com",
// "https://www.你的前端域名.com",
"http://localhost:10086",
"http://192.168.88.119:10086",
];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
// 允许哪个前端来源访问
res.set("Access-Control-Allow-Origin", origin);
// 否则 Nginx、Cloudflare 等可能缓存错 origin 的响应,线上会出现「偶发 CORS 失败」。
// 防止 CDN 把 A 域名的 CORS 响应缓存给 B 域名
res.set("Vary", "Origin");
// 允许跨域带 Cookie / 凭证
res.set("Access-Control-Allow-Credentials", "true");
}
next();
});
5. 方案二:前端配置 Proxy 代理
前端配置 proxy 代理,不需要后端配置跨域头。
devServer 代理配置
javascript
devServer: {
proxy: {
'/proxy': {
// target: 'http://localhost:8080',
target: 'http://192.168.88.119:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/proxy/, ''),
},
},
},
前端请求示例
javascript
const fetchData = () => {
// const url = 'http://192.168.88.119:8080/api/todos'; // 需要后端配置跨域头,否则会报错
const url = "/proxy/api/todos"; // 前端走代理,不需要后端配置跨域头
fetch(url)
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));
};