
什么是 CORS 以及它为何存在?
CORS(跨源资源共享)是一种浏览器安全机制,用于控制允许哪些跨源请求。当运行在 https://app.example.com 上的 JavaScript 尝试从 https://api.other.com(不同源:不同域名、端口或协议)获取数据时,就会发生跨源请求。
如果没有 CORS,任何网站都可以静默读取您银行、电子邮件或任何您已登录网站的数据。CORS 的存在正是为了防止这种情况:它让服务器声明它们信任哪些源。
重要的是,CORS 由浏览器强制执行,而非服务器。服务器仍然接收并处理请求——它只是在响应中包含(或不包含)权限头。然后浏览器决定是否将该响应暴露给 JavaScript。

最常见的 CORS 错误消息
"Access to XMLHttpRequest blocked by CORS policy: No 'Access-Control-Allow-Origin' header"
服务器已响应,但未包含 Access-Control-Allow-Origin 头。浏览器阻止响应到达您的 JavaScript。
修复: 在服务器上添加该头。
"Access to fetch blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://other.com' that is not equal to the supplied origin"
服务器已启用 CORS,但只允许与您不同的源。
修复: 确保您的源在服务器的允许源列表中。
"CORS preflight response did not succeed"
对于非简单请求(PUT、DELETE、自定义头),浏览器首先发送一个 OPTIONS 预检请求。如果该预检失败,实际请求永远不会发出。
修复: 在服务器上显式处理 OPTIONS 请求。
"Request header field Authorization is not allowed by Access-Control-Allow-Headers"
您的请求包含服务器未显式允许的头。
修复: 将头名称添加到 Access-Control-Allow-Headers。
什么使请求成为“简单”或“预检”请求?
简单请求(无预检):
- 方法为 GET、HEAD 或 POST
- 仅使用安全头:
Accept、Accept-Language、Content-Language、Content-Type(有限制) Content-Type为application/x-www-form-urlencoded、multipart/form-data或text/plain
预检请求(浏览器先发送 OPTIONS):
- 方法为 PUT、DELETE、PATCH 或任何其他方法
- 使用自定义头,如
Authorization、X-Custom-Header Content-Type为application/json
这意味着大多数现代应用的 API 调用都是预检的,因为它们使用 Content-Type: application/json 或发送 Authorization 头。

按框架的服务器端修复
Node.js / Express
const express = require('express');
const app = express();
// 选项 1:使用 cors 包(推荐)
const cors = require('cors');
app.use(cors({
origin: ['https://myapp.com', 'https://staging.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 如果发送 cookie/认证头则需要
maxAge: 86400, // 缓存预检 24 小时
}));
// 选项 2:手动设置头
app.use((req, res, next) => {
const allowedOrigins = ['https://myapp.com', 'https://staging.myapp.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
return res.sendStatus(204); // 预检响应
}
next();
});
Python / FastAPI
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://myapp.com", "https://staging.myapp.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Python / Django
# settings.py
INSTALLED_APPS = [
...
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 必须位于 CommonMiddleware 之前
'django.middleware.common.CommonMiddleware',
...
]
CORS_ALLOWED_ORIGINS = [
"https://myapp.com",
"https://staging.myapp.com",
]
CORS_ALLOW_CREDENTIALS = True
# 或者用于开发(绝不要用于生产):
# CORS_ALLOW_ALL_ORIGINS = True
Go / Gin
import "github.com/gin-contrib/cors"
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://myapp.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
Nginx(反向代理)
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://myapp.com' 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;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://backend;
}

credentials 陷阱
如果您的请求包含 cookie 或 Authorization 头,您必须:
- 在服务器上设置
Access-Control-Allow-Credentials: true - 将
Access-Control-Allow-Origin设置为特定源——通配符(*)不适用于凭据 - 在客户端:在 fetch 中设置
credentials: 'include',或在 Axios 中设置withCredentials: true
// fetch
fetch('https://api.example.com/data', {
credentials: 'include', // 发送 cookie
});
// Axios
axios.get('https://api.example.com/data', {
withCredentials: true,
});
如果您同时使用 Access-Control-Allow-Origin: * 和凭据,浏览器将抛出:"The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '' when the request's credentials mode is 'include'."*
开发环境变通方法(不适用于生产)
Vite / webpack 开发服务器代理
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: path => path.replace(/^/api/, ''),
},
},
},
};
这将 /api/* 请求通过 Vite 的服务器代理,使其对浏览器而言是同源的——无需 CORS 头。
浏览器扩展(仅测试)
像 "CORS Unblock" 这样的扩展会禁用浏览器中的 CORS 强制。适用于快速测试。绝不要在生产中使用,也绝不要发布依赖于此的代码。
安全:绝不要在已认证的 API 上使用 Access-Control-Allow-Origin: *
通配符允许任何源读取您的 API 响应。对于公共的、未认证的 API(CDN 资源、公共数据)这是可以的。对于任何使用认证或返回用户特定数据的 API,始终指定确切的允许源。
调试 CORS 问题
- 打开 DevTools → Network 标签 → 点击失败的请求 → 查看响应头
- 检查
Access-Control-Allow-Origin是否存在且与您的源匹配 - 对于预检请求,找到实际请求之前的 OPTIONS 请求并检查其响应
- 使用 curl 测试原始头:
curl -I -H "Origin: https://yourapp.com" https://api.example.com/endpoint
→ 使用 URL 解析器 分解 API 端点并检查其组件。