8.5 CORS 与跨域请求
8.5 CORS 与跨域请求
跨域资源共享(CORS)是现代Web开发中处理跨域请求的标准机制,它通过特定的HTTP头部允许服务器声明哪些外部源可以访问其资源。本节将深入解析CORS的工作原理、实现方式和最佳实践。
核心机制与流程
1. 同源策略基础
同源策略要求"协议+域名+端口"完全一致才允许直接通信。以下是典型同源检测结果示例:
| 请求URL | 当前源 | 是否同源 | 原因 |
|---|---|---|---|
https://example.com/api |
https://example.com |
是 | 完全一致 |
http://example.com/api |
https://example.com |
否 | 协议不同 |
https://api.example.com |
https://example.com |
否 | 子域名不同 |
https://example.com:8080 |
https://example.com |
否 | 端口不同 |
2. CORS工作流程
简单请求流程:
sequenceDiagram
Client->>Server: GET /resource
Server-->>Client: Access-Control-Allow-Origin: *
Client->>Client: 允许响应数据
**预检请求流程**:
sequenceDiagram
Client->>Server: OPTIONS /resource
Server-->>Client: Access-Control-Allow-Origin: https://example.com
Server-->>Client: Access-Control-Allow-Methods: PUT, DELETE
Client->>Server: PUT /resource
Server-->>Client: Access-Control-Allow-Origin: https://example.com
服务器端配置详解
1. Node.js Express配置
const express = require('express');
const cors = require('cors');
const app = express();
// 基本CORS配置(允许所有源)
app.use(cors());
// 高级配置
const corsOptions = {
origin: [
'https://example.com',
'https://dev.example.com',
'http://localhost:3000'
],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Custom-Header'],
credentials: true,
maxAge: 86400,
preflightContinue: false,
optionsSuccessStatus: 204
};
app.use('/api', cors(corsOptions), apiRouter);
// 动态origin回调
const dynamicCorsOptions = {
origin: function (origin, callback) {
if (!origin || whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
};
2. Nginx反向代理配置
server {
listen 80;
server_name api.example.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,Content-Type';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://example.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
proxy_pass http://backend;
}
}
客户端处理方案
1. Fetch API使用规范
// 基础跨域请求
fetch('https://api.example.com/data', {
method: 'GET',
mode: 'cors', // 默认值,可省略
headers: {
'Content-Type': 'application/json'
},
credentials: 'include' // 需要携带cookie时
})
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.json();
})
.catch(error => {
console.error('请求失败:', error);
});
// 预检请求示例
fetch('https://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
body: JSON.stringify({ key: 'value' })
});
2. 错误处理与调试
async function safeFetch(url, options = {}) {
try {
const response = await fetch(url, {
...options,
mode: 'cors'
});
// 检查CORS头
if (!response.headers.get('access-control-allow-origin')) {
throw new Error('Missing CORS headers');
}
// 处理不同响应类型
const contentType = response.headers.get('content-type');
if (contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (error) {
console.error('CORS请求失败:', {
url,
error: error.message,
type: error.name
});
// 回退方案
return fallbackRequest(url);
}
}
// 使用示例
safeFetch('https://api.example.com/data')
.then(data => console.log('响应数据:', data));
高级场景处理
1. 携带凭据的请求
// 服务器配置
const corsOptions = {
origin: 'https://example.com',
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization']
};
// 客户端请求
fetch('https://api.example.com/user', {
credentials: 'include',
headers: {
'Authorization': `Bearer ${token}`
}
});
2. 自定义头部处理
// 预检请求需要的配置
app.options('/special', cors({
allowedHeaders: ['X-Custom-Header', 'X-Extra-Header'],
methods: ['POST']
}));
app.post('/special', cors(), (req, res) => {
res.json({ status: 'ok' });
});
// 客户端发送自定义头
fetch('https://api.example.com/special', {
method: 'POST',
headers: {
'X-Custom-Header': 'value',
'Content-Type': 'application/json'
}
});
安全最佳实践
1. 生产环境配置建议
// 严格的白名单控制
const whitelist = [
'https://example.com',
'https://www.example.com',
'https://staging.example.com'
];
const corsOptionsDelegate = function (req, callback) {
let corsOptions;
const origin = req.header('Origin');
if (whitelist.indexOf(origin) !== -1) {
corsOptions = {
origin: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type'],
maxAge: 600
};
} else {
corsOptions = { origin: false };
}
callback(null, corsOptions);
};
app.use(cors(corsOptionsDelegate));
2. 常见漏洞防护
反射型CORS滥用防护:
// 不安全的配置示例(避免!)
app.use(cors({
origin: true, // 反射请求的Origin头
credentials: true
}));
// 安全替代方案
app.use(cors({
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
// 非白名单源仍然返回200但不带CORS头
callback(null, false);
}
}
}));
性能优化策略
1. 预检缓存优化
# 服务器响应头示例
Access-Control-Max-Age: 86400 # 缓存1天(秒)
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
2. CDN层CORS配置
# AWS CloudFront行为配置示例
Response headers policy:
- Access-Control-Allow-Origin: https://example.com
- Access-Control-Allow-Methods: GET, HEAD, OPTIONS
- Access-Control-Max-Age: 600
- Access-Control-Expose-Headers: Date
调试工具与技巧
1. Chrome开发者工具
- Network面板:查看CORS相关请求头/响应头
- Console警告:识别CORS策略违规
- 命令行检测:
// 检查CORS配置 fetch('https://api.example.com', { method: 'OPTIONS' }) .then(res => console.log(res.headers));
2. 常见错误解析
| 错误信息 | 原因分析 | 解决方案 |
|---|---|---|
No 'Access-Control-Allow-Origin' |
服务器未返回CORS头 | 检查服务器配置 |
Credentials not supported |
服务器未设置allow-credentials |
服务器添加allow-credentials: true |
Method not allowed |
预检请求未包含该方法 | 更新allow-methods头 |
Header not allowed |
请求包含未允许的头部 | 扩展allow-headers或简化请求 |
替代方案比较
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| CORS | 标准、安全、灵活 | 需要服务器配合 | 可控的跨域API调用 |
| JSONP | 兼容老旧浏览器 | 仅GET、安全性差 | 遗留系统兼容 |
| 代理服务器 | 完全避免跨域问题 | 额外性能开销 | 无法修改API服务时 |
| WebSocket | 双向通信、低延迟 | 协议不同、复杂度高 | 实时应用 |
实际应用案例
1. 跨域AJAX上传
// 客户端代码
const uploadFile = (file) => {
const formData = new FormData();
formData.append('file', file);
return fetch('https://storage.example.com/upload', {
method: 'POST',
body: formData,
headers: {
'X-Auth-Token': 'secret123'
},
credentials: 'include'
});
};
// 服务器配置
app.post('/upload', cors({
origin: 'https://app.example.com',
allowedHeaders: ['X-Auth-Token', 'Content-Type'],
credentials: true
}), uploadHandler);
2. 多域名SSO集成
// 身份验证服务配置
const ssoCorsOptions = {
origin: [
'https://portal.example.com',
'https://app.example.com',
'https://internal.example.com'
],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'X-Requested-With'],
credentials: true,
maxAge: 3600
};
app.use('/auth', cors(ssoCorsOptions), authRouter);
// 客户端检查
if (crossOriginIsolated) {
// 使用SharedArrayBuffer等高级特性
} else {
console.warn('页面未处于跨域隔离状态');
}
CORS作为现代Web开发的核心安全机制,正确实施需要注意:
- 生产环境避免使用
*通配符 - 严格限制
allow-credentials的使用范围 - 为敏感操作设置合理的
max-age - 结合内容安全策略(CSP)提供纵深防御
- 定期审计CORS配置与使用情况
通过合理配置CORS策略,开发者可以在保障安全的前提下,构建功能丰富的跨源Web应用。
#前端开发
分享于 2025-05-20
上一篇:8.4 postMessage 与跨文档通信
下一篇:9.1 地理位置 API