
在前后端分离的Web开发中,跨域请求是最常遇到的问题之一。当你在前端调用不同域名的API时,浏览器会抛出经典的CORS错误。很多开发者只知道加上Access-Control-Allow-Origin头就能解决,但对背后的原理一知半解。本文将从同源策略讲起,深入剖析CORS的工作机制,并给出在各种后端框架中的完整解决方案。
什么是同源策略
同源策略(Same-Origin Policy)是浏览器最核心的安全机制之一。当两个URL的协议(protocol)、域名(host)和端口(port)完全相同时,才被认为是同源的。只要有一个不同,就是跨域。
// 同源判断示例
const current = "http://example.com:80/path";
"http://example.com:80/other" // ✅ 同源
"https://example.com:80/path" // ❌ 协议不同
"http://api.example.com:80/path" // ❌ 域名不同
"http://example.com:8080/path" // ❌ 端口不同
同源策略限制了以下行为:无法读取非同源页面的Cookie、LocalStorage、DOM,也无法通过XHR/Fetch向非同源地址发送请求(实际上请求发出去了,但浏览器拦截了响应)。
CORS请求的两种类型
CORS(Cross-Origin Resource Sharing)是W3C标准,允许服务器声明哪些源可以访问其资源。根据请求的复杂程度,分为简单请求和预检请求两种。
简单请求需要同时满足以下条件:方法为GET、HEAD或POST之一;Content-Type为text/plain、multipart/form-data或application/x-www-form-urlencoded;不包含自定义请求头。简单请求会直接发送,浏览器根据响应头决定是否放行。
// 简单请求示例 - 直接发送
fetch("https://api.example.com/data", {
method: "GET",
headers: {
"Accept": "application/json"
}
}).then(res => res.json())
.then(data => console.log(data));
预检请求(Preflight):不满足简单请求条件时,浏览器会先发送一个OPTIONS方法的预检请求,询问服务器是否允许实际请求。只有预检通过后,才会发送真正的请求。
// 预检请求 - 浏览器自动先发OPTIONS
fetch("https://api.example.com/data", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
"X-Custom-Header": "value"
},
body: JSON.stringify({ key: "value" })
});
// 浏览器先发 OPTIONS /data 请求
// 服务器返回 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers
// 通过后才发真正的 PUT 请求
完整的CORS响应头解析
服务器需要返回一系列CORS相关的响应头来控制跨域访问权限:
Access-Control-Allow-Origin: https://example.com # 允许的源( 表示所有)
Access-Control-Allow-Methods: GET, POST, PUT, DELETE # 允许的方法
Access-Control-Allow-Headers: Content-Type, Authorization # 允许的自定义头
Access-Control-Allow-Credentials: true # 是否允许携带Cookie
Access-Control-Max-Age: 86400 # 预检结果缓存时间(秒)
Access-Control-Expose-Headers: X-Custom-Header # 前端可读取的响应头
特别注意:当Access-Control-Allow-Origin设为通配符时,Access-Control-Allow-Credentials不能同时设为true。两者互斥,必须指定具体的源。

主流后端框架的CORS配置
Node.js + Express:
const cors = require("cors");
// 开发环境:允许所有源
app.use(cors());
// 生产环境:精确配置
app.use(cors({
origin: ["https://app.example.com", "https://admin.example.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
maxAge: 86400
}));
Python Flask:
from flask import Flask
from flask_cors import CORS
app = Flask(name)
全局配置
CORS(app, resources={
r"/api/*": {
"origins": ["https://app.example.com"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True,
"max_age": 86400
}
})
Nginx反向代理配置:
server {
listen 443 ssl;
server_name api.example.com;location /api/ { # 动态设置允许的源 set $cors_origin ""; if ($http_origin ~* "^https://(app|admin)\.example\.com$") { set $cors_origin $http_origin; } add_header Access-Control-Allow-Origin $cors_origin always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; add_header Access-Control-Allow-Credentials "true" always; add_header Access-Control-Max-Age 86400 always; # 处理预检请求 if ($request_method = "OPTIONS") { return 204; } proxy_pass http://127.0.0.1:8000/; }}
常见问题排查清单
遇到CORS问题时,按以下顺序排查:
1. 多个CORS头冲突:Nginx和应用框架同时设置了CORS头,导致出现重复的Access-Control-Allow-Origin。解决方案是在Nginx层面统一处理,去掉应用层的CORS中间件。
2. Cookie无法携带:前端需要设置credentials: ‘include’,后端必须指定具体的Origin且Allow-Credentials为true。
// 前端携带Cookie的正确方式
fetch("https://api.example.com/data", {
credentials: "include" // 不能用 * 的 Origin
});3. 自定义响应头前端读不到:需要在Access-Control-Expose-Headers中声明,否则前端虽然收到了响应,但JavaScript无法访问该头部。
4. 预检请求被404:确保服务器正确处理OPTIONS方法请求,很多开发者只配置了GET/POST路由,忽略了OPTIONS。
总结
CORS的核心要点:同源策略是浏览器安全基石,CORS是服务器主动放开限制的机制;区分简单请求和预检请求有助于理解请求流程;生产环境务必指定具体域名而非通配符*;Nginx反代时注意避免与应用层CORS头重复。掌握这些,跨域问题将不再是开发障碍。
汤不热吧