**创建时间:** 2026-03-27
**更新时间:** 2026-03-27
**阅读时间:** 约 12 分钟
---
CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种安全机制,用于限制 Web 应用程序如何从一个域请求另一个域的资源。它是浏览器实施的同源策略(Same-Origin Policy)的扩展。
同源策略是浏览器的核心安全特性,它阻止一个源的文档或脚本与另一个源的资源进行交互。这里的"源"由三部分组成:
| 组成部分 | 说明 | 示例 |
|----------|------|------|
| 协议(Protocol) | 使用的通信协议 | http、https |
| 域名(Domain) | 服务器的域名 | example.com |
| 端口(Port) | 服务器端口号 | 80、443、8080 |
只有当这三个部分完全相同时,才被认为是"同源"。
✅ 同源(允许访问):
https://example.com/page1.html → https://example.com/page2.html
https://example.com:443/api → https://example.com:443/data
❌ 跨域(受 CORS 限制):
https://example.com → https://api.example.com(域名不同)
http://example.com → https://example.com(协议不同)
https://example.com:8080 → https://example.com:443(端口不同)
---
简单请求 vs 预检请求流程对比
sequenceDiagram
participant B as 浏览器
participant F as 前端 origin: example.com
participant S as API api.other.com
Note over B,F,S: 简单请求 (GET/POST + 简单类型)
B->>S: GET /data
Origin: example.com
S->>B: 200 OK
Access-Control-Allow-Origin: *
Note over B: 检查 ACAH 存在
允许跨域
Note over B,F,S: 预检请求 (复杂请求)
B->>S: OPTIONS /data
Origin: example.com
Access-Control-Request-Method: PUT
S->>B: 200 OK
Access-Control-Allow-Methods: PUT
B->>S: PUT /data
Origin: example.com
S->>B: 200 OK
Access-Control-Allow-Origin: *
满足以下所有条件的请求被视为简单请求:
GET、HEAD、POST - Accept
- Accept-Language
- Content-Language
- Content-Type(仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain)
**简单请求流程:**
浏览器 服务器
| |
|-------- 带 Origin 头的请求 ------------->|
| Origin: https://example.com |
| |
|<------- 带 CORS 头的响应 ---------------|
| Access-Control-Allow-Origin: |
| https://example.com |
| |
不满足简单请求条件的请求会先发送一个 OPTIONS 预检请求,询问服务器是否允许实际请求。
**预检请求流程:**
浏览器 服务器
| |
|-------- OPTIONS 预检请求 -------------->|
| Origin: https://example.com |
| Access-Control-Request-Method: |
| POST |
| Access-Control-Request-Headers: |
| Content-Type, Authorization |
| |
|<------- 预检响应 -----------------------|
| Access-Control-Allow-Origin: |
| https://example.com |
| Access-Control-Allow-Methods: |
| POST, GET, OPTIONS |
| Access-Control-Allow-Headers: |
| Content-Type, Authorization |
| Access-Control-Max-Age: 86400 |
| |
|-------- 实际请求 ---------------------->|
| (预检通过后) |
| |
---
CORS 错误排查流程
flowchart TD
A[浏览器报 CORS 错误] --> B{响应头有
ACAO?}
B -->|无| C[服务端添加
Access-Control-Allow-Origin]
C --> D[重新请求]
B -->|有| E{Origin 匹配?}
E -->|不匹配| F[ACAO 设为具体域名
而非 *]
F --> D
E -->|匹配| G{凭证模式?}
G -->|带 Cookie| H[ACAO 不能为 *
需具体域名 +
Access-Control-Allow-Credentials: true]
G -->|不带凭证| I[浏览器允许跨域]
H --> D
D --> A
**错误信息:**
Access to fetch at 'https://api.example.com/data' from origin 'https://example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
**原因:** 服务器响应中缺少 Access-Control-Allow-Origin 头。
**解决方案:**
location /api/ {
add_header Access-Control-Allow-Origin https://example.com;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
# 处理预检请求
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin https://example.com;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Max-Age 86400;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
Header set Access-Control-Allow-Origin "https://example.com"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
# 处理预检请求
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
const express = require('express');
const cors = require('cors');
const app = express();
// 允许特定域名
app.use(cors({
origin: 'https://example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
// 或手动设置头信息
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://example.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
允许特定域名
CORS(app, resources={
r"/api/*": {
"origins": "https://example.com",
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"]
}
})
或手动设置头信息
@app.after_request
def add_cors_headers(response):
response.headers['Access-Control-Allow-Origin'] = 'https://example.com'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
return response
---
**错误信息:**
The value of the 'Access-Control-Allow-Origin' header in the response must not be
the wildcard '*' when the request's credentials mode is 'include'.
**原因:** 当请求包含凭证(cookies、认证信息)时,Access-Control-Allow-Origin 不能使用通配符 *。
**解决方案:**
// ❌ 错误配置(当使用 credentials 时)
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// ✅ 正确配置(指定具体域名)
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
**前端请求配置:**
// 需要包含凭证的请求
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include', // 包含 cookies
headers: {
'Content-Type': 'application/json'
}
});
// Axios 配置
axios.get('https://api.example.com/data', {
withCredentials: true, // 包含 cookies
headers: {
'Content-Type': 'application/json'
}
});
---
**错误信息:**
Access to fetch at 'https://api.example.com/data' from origin 'https://example.com'
has been blocked by CORS policy: Response to preflight request doesn't pass access
control check: No 'Access-Control-Allow-Headers' header is present on the requested
resource.
**原因:** 预检请求中请求的头信息未被服务器允许。
**解决方案:**
确保服务器响应中包含 Access-Control-Allow-Headers,并列出所有客户端请求中使用的自定义头:
Nginx 配置
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, X-Custom-Header";
// Node.js 配置
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, X-Custom-Header');
---
**错误信息:**
Access to fetch at 'https://api.example.com/data' from origin 'https://example.com'
has been blocked by CORS policy: Method PATCH is not allowed by Access-Control-Allow-Methods
in preflight response.
**原因:** 使用的 HTTP 方法(如 PATCH)未在服务器的 Access-Control-Allow-Methods 中声明。
**解决方案:**
Nginx 配置
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS";
// Node.js 配置
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
---
关键 CORS 响应头一览
flowchart LR
H["Access-Control-Allow-Origin
必选,* 或具体域名"]
M["Access-Control-Allow-Methods
预检响应,允许的方法"]
MH["Access-Control-Allow-Headers
预检响应,允许的请求头"]
C["Access-Control-Allow-Credentials
是否接受 Cookie"]
E["Access-Control-Expose-Headers
允许 JS 访问的响应头"]
MX["Access-Control-Max-Age
预检结果缓存时间"]
H --> M --> MH --> C --> E --> MX
| 头信息 | 说明 | 示例 |
|--------|------|------|
| Access-Control-Allow-Origin | 指定允许访问的源 | https://example.com 或 * |
| Access-Control-Allow-Methods | 允许的 HTTP 方法 | GET, POST, PUT, DELETE |
| Access-Control-Allow-Headers | 允许的请求头 | Content-Type, Authorization |
| Access-Control-Allow-Credentials | 是否允许发送凭证 | true 或 false |
| Access-Control-Expose-Headers | 允许暴露给浏览器的响应头 | X-Custom-Header |
| Access-Control-Max-Age | 预检请求结果缓存时间(秒) | 86400(24 小时) |
| 头信息 | 说明 | 示例 |
|--------|------|------|
| Origin | 请求来源 | https://example.com |
| Access-Control-Request-Method | 预检请求中声明的方法 | POST |
| Access-Control-Request-Headers | 预检请求中声明的头 | Content-Type |
---
在开发环境中,前端和后端通常运行在不同端口:
前端:http://localhost:3000
后端:http://localhost:8080
**开发环境 Nginx 配置:**
server {
listen 8080;
location /api/ {
# 开发环境允许 localhost
add_header Access-Control-Allow-Origin http://localhost:3000;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Allow-Credentials true;
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin http://localhost:3000;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Max-Age 86400;
return 204;
}
proxy_pass http://backend:8080;
}
}
**Vue CLI 配置(vue.config.js):**
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
**React (Create React App) 配置(package.json):**
{
"proxy": "http://localhost:8080"
}
**Vite 配置(vite.config.js):**
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
---
// ✅ 推荐:明确指定允许的域名
const allowedOrigins = [
'https://example.com',
'https://www.example.com',
'https://app.example.com'
];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
}
next();
});
// .env 文件
ALLOWED_ORIGINS=https://example.com,https://app.example.com
CORS_MAX_AGE=86400
// 代码中使用
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
const maxAge = parseInt(process.env.CORS_MAX_AGE) || 86400;
缓存预检结果 24 小时,减少预检请求
add_header Access-Control-Max-Age 86400;
仅对 API 路由启用 CORS,静态资源不需要
location /api/ {
add_header Access-Control-Allow-Origin https://example.com;
# ... 其他 CORS 配置
}
location /static/ {
# 静态资源不需要 CORS
expires 1y;
}
---
❌ 不要在生产环境使用通配符 + credentials
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
❌ 不要动态反射任意 Origin(可能导致 XSS)
如果必须动态设置,请验证白名单
function isValidOrigin(origin) {
const allowedOrigins = [
'https://example.com',
'https://www.example.com',
'https://app.example.com'
];
// 验证 origin 格式
try {
const url = new URL(origin);
if (url.protocol !== 'https:') {
return false; // 只允许 HTTPS
}
} catch {
return false;
}
return allowedOrigins.includes(origin);
}
app.use((req, res, next) => {
const origin = req.headers.origin;
if (origin && isValidOrigin(origin)) {
res.header('Access-Control-Allow-Origin', origin);
}
next();
});
---
| 场景 | Access-Control-Allow-Origin | Access-Control-Allow-Credentials |
|------|----------------------------|----------------------------------|
| 公开 API(无需凭证) | * | 不需要 |
| 需要 Cookie 的 API | 具体域名 | true |
| 多域名支持 | 动态验证白名单 | 根据需求 |
| 开发环境 | http://localhost:3000 | 可选 |
| 方法 | 简单请求 | 需要预检 |
|------|----------|----------|
| GET | ✅ | ❌ |
| HEAD | ✅ | ❌ |
| POST | ✅(特定 Content-Type) | ❌ |
| PUT | ❌ | ✅ |
| DELETE | ❌ | ✅ |
| PATCH | ❌ | ✅ |
| OPTIONS | ❌ | ✅(预检本身) |
---
---
**最后更新:** 2026-03-27
**作者:** 网事小栈