Node系列 · Express:cors 中间件
上一章讲了 CORS 原理——这一章用
cors中间件把它从手动配置响应头简化成一行app.use(cors())。
一、安装与最简配置
bash
npm install corsjavascript
const cors = require('cors');
// 允许所有跨域(仅开发环境!)
app.use(cors());这一行相当于手动加了:
javascript
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, ...');
next();
});
app.options('*', cors()); // 自动处理预检二、生产配置
2.1 指定允许的 Origin
javascript
const cors = require('cors');
const ALLOWED_ORIGINS = [
'https://app.example.com',
'https://admin.example.com',
];
app.use(cors({
origin: (origin, callback) => {
// origin 在同源请求时为 undefined(直接放行)
if (!origin || ALLOWED_ORIGINS.includes(origin)) {
return callback(null, true);
}
callback(new Error('CORS not allowed'));
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400, // 预检缓存 24 小时
}));2.2 关键 options
| 选项 | 默认 | 说明 |
|---|---|---|
origin | * | 允许的源 |
methods | GET, HEAD, PUT, PATCH, POST, DELETE | 允许的方法 |
allowedHeaders | Origin, X-Requested-With, ... | 允许的请求头 |
exposedHeaders | (空) | 浏览器可读取的响应头 |
credentials | false | 是否允许 Cookie |
maxAge | 5(秒) | 预检缓存时间 |
2.3 origin 三种写法
javascript
// 1. 字符串(具体域名或 *)
cors({ origin: 'https://app.example.com' });
cors({ origin: '*' });
// 2. 数组
cors({ origin: ['https://a.com', 'https://b.com'] });
// 3. 函数(动态判断)
cors({
origin: (origin, callback) => {
if (WHITELIST.includes(origin)) {
callback(null, true); // 允许
} else {
callback(new Error('Not allowed'));
}
},
});三、携带 Cookie
javascript
app.use(cors({
origin: 'https://app.example.com', // 不能是 *
credentials: true,
}));客户端配合:
javascript
fetch('https://api.example.com/users', {
credentials: 'include', // 携带 Cookie
});四、只对部分路由生效
不必全局 app.use(cors())——可以只对 API 路由生效:
javascript
// 公共 API:允许所有跨域
app.use('/api/public', cors());
// 私有 API:只允许特定 Origin + 携带 Cookie
app.use('/api/private', cors({
origin: 'https://app.example.com',
credentials: true,
}));
// 不需要跨域的内部路由:不挂 cors
app.use('/api/internal', requireAuth);五、自定义成功 / 失败响应
javascript
app.use(cors({
origin: (origin, callback) => {
if (!WHITELIST.includes(origin)) {
return callback(new Error('CORS blocked'));
}
callback(null, true);
},
}));
// CORS 失败的统一错误处理
app.use((err, req, res, next) => {
if (err.message === 'CORS blocked') {
return res.status(403).json({ error: 'CORS not allowed' });
}
next(err);
});六、不同环境差异化配置
javascript
const isDev = process.env.NODE_ENV !== 'production';
app.use(cors(isDev
? {
// 开发环境:宽松
origin: true, // 反射所有 Origin
credentials: true,
}
: {
// 生产环境:严格白名单
origin: (origin, cb) => {
if (PROD_ORIGINS.includes(origin)) {
cb(null, true);
} else {
cb(new Error('CORS blocked'));
}
},
credentials: true,
maxAge: 86400,
}
));七、常见问题
7.1 cors() 不生效?
中间件顺序问题——cors 必须在路由前注册:
javascript
// ✅ 正确
app.use(cors());
app.use(express.json());
app.use('/api/users', userRoutes);
// ❌ 错误:路由先于 cors
app.use('/api/users', userRoutes);
app.use(cors()); // 太晚了7.2 预检失败?
浏览器 OPTIONS 请求的响应没有 CORS 头:
bash
$ curl -X OPTIONS https://api.example.com/users \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST"
# 看响应头里有没有:
# Access-Control-Allow-Origin
# Access-Control-Allow-Methods
# Access-Control-Allow-Headerscors 中间件会自动处理 OPTIONS 预检响应——前提是配置正确。
7.3 Cookie 没带上?
检查三处:
| 位置 | 必须 |
|---|---|
| 服务端 | credentials: true + 具体 Origin(不能 *) |
| 客户端 | credentials: 'include' |
| 浏览器 | 看到 Access-Control-Allow-Credentials: true 才带 Cookie |
八、最佳实践
| 场景 | 推荐 |
|---|---|
| 开发环境 | app.use(cors()) 一行搞定 |
| 生产环境 | 动态白名单 + credentials: true + maxAge: 86400 |
| 多环境 | 用环境变量控制 origin 列表 |
| 只对部分路由 | 局部 app.use('/api/xxx', cors(...)) |
| 安全 | 永远不要在生产用 origin: '*' |
| 缓存 | 多 Origin 配 Vary: Origin(cors 中间件自动加) |
九、小结
cors中间件把手动配响应头简化成app.use(cors())- 生产环境用
origin: 函数做白名单校验 - 携带 Cookie 必须三件套:服务端
credentials: true+ 具体 Origin + 客户端credentials: 'include' - 中间件顺序:
cors→express.json→ 业务路由 cors自动处理 OPTIONS 预检,但配置错误仍会失败- 开发用宽松配置,生产用严格白名单
