一、跨域本质解析
跨域(CORS) 是由浏览器的同源策略(Same-Origin Policy) 引发的安全机制,当协议、域名、端口任一不同时即产生跨域。其核心矛盾在于:
- 浏览器层面:禁止跨域请求(XMLHttpRequest/Fetch API)
- 服务器层面:实际已收到请求并返回数据
同源策略示例对比:
| 请求地址 | 目标地址 | 是否跨域 | 原因 |
|------------------------|------------------------|----------|----------------------|
| https://a.com | https://a.com/api | 否 | 协议/域名/端口相同 |
| http://a.com | https://a.com | 是 | 协议不同(http vs https) |
| http://a.com:8080 | http://a.com:3000 | 是 | 端口不同 |
| http://sub.a.com | http://a.com | 是 | 子域名不同 |
二、跨域请求执行流程
sequenceDiagram
participant Browser
participant Server
Browser->>Server: 预检请求(OPTIONS)
Note right of Server: 检查CORS头信息
Server-->>Browser: 返回允许的策略
Browser->>Server: 实际请求(GET/POST等)
Server-->>Browser: 返回带CORS头的真实数据
三、九大跨域解决方案对比
| 方案 | 实现原理 | 适用场景 | 优缺点 |
|---|---|---|---|
| CORS | 服务端设置响应头 | 现代浏览器标准方案 | ✅ 标准安全 ❌ 需服务端配合 |
| JSONP | 利用 | 仅GET请求 | ✅ 兼容性好 ❌ 安全性差 |
| 反向代理 | 服务端中转请求 | 开发环境/无控制权的API | ✅ 完全控制 ❌ 增加架构复杂度 |
| WebSocket | 使用ws协议通信 | 实时通信场景 | ✅ 双向通信 ❌ 协议切换成本 |
| postMessage | Window对象消息传递 | 跨窗口通信 | ✅ 跨域文档通信 ❌ 需交互方配合 |
| document.domain | 强制同源 | 同主域不同子域 | ✅ 简单有效 ❌ 仅限同主域 |
| Nginx代理 | 网关层转发请求 | 生产环境部署 | ✅ 高性能 ❌ 需运维知识 |
| iframe嵌套 | 隐藏跨域实现 | 老系统兼容 | ✅ 兼容性佳 ❌ 安全性隐患 |
| Access-Control-* | 手动设置响应头 | 简单跨域需求 | ✅ 快速实现 ❌ 配置较繁琐 |
四、Node.js解决方案
graph TD
A[跨域解决方案] --> B[CORS中间件]
A --> C[代理服务器]
A --> D[手动设置响应头]
B --> E[express/cors]
B --> F[koa/cors]
C --> G[http-proxy-middleware]
C --> H[nginx反向代理]
D --> I[设置Access-Control头]
1. CORS中间件(推荐)
npm install cors
2. 代理中间件
const { createProxyMiddleware } = require('http-proxy-middleware');
app.use('/api', createProxyMiddleware({
target: 'http://backend:3000',
changeOrigin: true
}));
3. 手动设置响应头
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
});
五、CORS中间件深度配置
1. 安装与基础使用
npm install cors
2. 中间件配置模板
const express = require('express');
const cors = require('cors');
const app = express();
// 基础配置
app.use(cors({
origin: 'https://your-domain.com', // 或数组形式['http://a.com','http://b.com']
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Custom-Header'],
credentials: true,
maxAge: 86400,
preflightContinue: false,
optionsSuccessStatus: 204
}));
3. 动态origin配置示例
const whitelist = ['https://example.com', 'http://localhost:3000'];
app.use(cors({
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true
}));
4. 预检请求处理机制
// 手动处理OPTIONS请求
app.options('/api/data', cors({
origin: 'https://client-app.com',
methods: ['POST'],
allowedHeaders: ['X-Custom-Header']
}));
六、配置项逐项解析
1. origin(核心配置)
// 配置方式
origin: '*' // 允许所有域(生产环境禁用)
origin: 'https://client.com' // 单一指定域
origin: ['http://a.com', 'https://b.com'] // 多域白名单
origin: (origin, callback) => { // 动态验证
if(whitelist.includes(origin)) callback(null, true)
else callback(new Error('CORS blocked'))
}
解决的问题:
- 控制允许访问的客户端来源
- 防止CSRF攻击和未授权访问
- 动态处理多环境配置(开发/生产)
典型场景:
- 开发环境设置为
true或'*' - 生产环境使用精确域名白名单
- 需要携带Cookie时必须指定具体域名(不能使用通配符)
2. methods
methods: 'GET,POST,PUT,DELETE' // 字符串形式
methods: ['GET', 'POST'] // 数组形式
解决的问题:
- 限制允许的HTTP方法类型
- 减少不必要的方法暴露
- 符合RESTful API设计规范
注意事项:
- 必须包含实际使用的请求方法
- OPTIONS方法由中间件自动处理
3. allowedHeaders
allowedHeaders: ['Content-Type', 'Authorization']
解决的问题:
- 控制客户端可发送的请求头
- 防止恶意头注入攻击
- 匹配前端实际使用的请求头
典型配置:
- Content-Type:必选(用于POST/PUT请求)
- Authorization:JWT认证场景
- 自定义头需要明确声明
4. exposedHeaders
exposedHeaders: ['X-Total-Count']
解决的问题:
- 允许客户端访问自定义响应头
- 突破浏览器默认头可见性限制
应用场景:
- 分页接口返回总记录数
- 自定义监控头信息
- OAuth认证相关头
5. credentials
credentials: true
解决的问题:
- 允许跨域携带Cookie/HTTP认证
- 启用跨域会话保持
必要条件:
- 前端需要设置
withCredentials: true - origin不能使用通配符
* - 需要配合allowedHeaders包含认证头
6. maxAge
maxAge: 86400 // 单位:秒(1天)
解决的问题:
- 控制预检请求缓存时间
- 减少OPTIONS请求次数
- 提升接口性能
优化建议:
- 高频接口设置较长缓存
- 低频敏感接口适当缩短
7. preflightContinue
preflightContinue: false
配置影响:
false:由中间件自动处理OPTIONS请求true:将OPTIONS请求传递给后续中间件
使用场景:
- 需要自定义OPTIONS处理逻辑时开启
- 99%场景保持默认false即可
8. optionsSuccessStatus
optionsSuccessStatus: 204
设计原因:
- 某些老旧设备无法处理204状态码
- 可强制返回200状态码
推荐配置:
- 现代浏览器保持204(默认)
- 兼容旧设备时设置为200
七、安全增强配置方案
1. 环境区分配置
const isProd = process.env.NODE_ENV === 'production';
const corsConfig = {
origin: isProd
? ['https://prod-client.com', 'https://admin.prod.com']
: 'http://localhost:3000',
credentials: isProd,
allowedHeaders: isProd
? ['Content-Type', 'Authorization', 'X-Request-ID']
: '*'
};
app.use(cors(corsConfig));
2. 速率限制+ CORS
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100,
message: 'Too many requests from this IP, please try again later'
});
app.use('/api/', cors(), apiLimiter);
3. 错误处理中间件
app.use((err, req, res, next) => {
if (err.message.includes('CORS')) {
res.status(403).json({
error: 'CORS Policy Violation',
message: err.message,
docs: 'https://api.example.com/cors-docs'
});
} else {
next(err);
}
});
八、常见问题解决方案
1. 携带Cookie场景
// 前端
fetch(url, { credentials: 'include' });
// 后端
app.use(cors({
origin: 'https://client.com',
credentials: true,
allowedHeaders: ['Content-Type', 'Cookie']
}));
2. 复杂头信息处理
app.use(cors({
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Custom-Header',
'X-Requested-With'
],
exposedHeaders: [
'Content-Length',
'X-Content-Length',
'X-Total-Count'
]
}));
3. 多环境配置方案
const devCors = {
origin: true, // 反射请求源
credentials: true
};
const prodCors = {
origin: 'https://production-domain.com',
optionsSuccessStatus: 200,
credentials: true
};
app.use(cors(process.env.NODE_ENV === 'production' ? prodCors : devCors));
4. 处理预检请求
// 显式处理OPTIONS
app.options('/special-endpoint', cors({
origin: 'https://special-client.com',
methods: 'PUT',
allowedHeaders: ['X-Custom-Header']
}));
5. 大文件上传优化
app.use(cors({
origin: true,
allowedHeaders: ['Content-Type', 'Content-Length'],
maxAge: 3600 // 延长预检缓存
}));
九、性能优化策略
1. 预检缓存优化
app.use(cors({
maxAge: 86400 // 浏览器缓存1天
}));
2. 按路由精细控制
// 公共API允许所有来源
app.get('/public-api', cors(), (req, res) => {
res.json({ data: 'public' });
});
// 私有API限制来源
app.post('/private-api', cors({
origin: 'https://trusted-domain.com'
}), (req, res) => {
res.json({ data: 'private' });
});
十、监控与调试方案
1. 日志记录中间件
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] Origin: ${req.headers.origin}`);
next();
});
2. 错误处理中间件
app.use((err, req, res, next) => {
if (err.message.includes('CORS')) {
res.status(403).json({
error: '跨域请求被拒绝',
detail: err.message
});
} else {
next(err);
}
});
十一、替代方案实现
1. 手动设置响应头
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://allowed-domain.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Max-Age', '86400');
next();
});
2. 代理服务器实现
const { createProxyMiddleware } = require('http-proxy-middleware');
app.use('/api', createProxyMiddleware({
target: 'http://internal-service:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}));
总结建议
- 开发环境:使用
cors中间件+动态origin配置 - 生产环境:严格限制origin白名单+反向代理
- 敏感接口:结合JWT鉴权+CSRF Token双重验证
- 性能关键:启用预检缓存+合理设置maxAge
- 安全增强:配合Helmet中间件设置安全头
通过合理配置CORS策略,既能保障系统安全性,又能满足现代Web应用复杂的跨域需求。建议结合具体业务场景选择最适合的解决方案,并建立完善的跨域请求监控机制。