feat: 实现可配置的 CORS 安全策略

- 添加 getCorsConfig() 函数支持灵活的 CORS 配置
- 支持三种模式:禁用 CORS、白名单、允许所有来源
- 环境变量可覆盖 config.json 配置 (CORS_ENABLED, CORS_ALLOW_ALL, CORS_ORIGINS)
- config.json 默认使用白名单模式,仅允许 localhost
- 动态验证 Origin 头,不在白名单的请求不设置 CORS 头
- 添加 Vary: Origin 头支持 CDN 缓存

安全改进:
- 生产环境默认 allow_all=false,避免 CORS 通配符
- 白名单模式下,未授权来源的请求会被浏览器拒绝

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2025-12-27 15:33:04 +08:00
parent 3dccbcfed1
commit eef909c5dd
4 changed files with 90 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
import express from 'express';
import { loadConfig, isDevMode, getPort } from './config.js';
import { loadConfig, isDevMode, getPort, getCorsConfig } from './config.js';
import { logInfo, logError } from './logger.js';
import router from './routes.js';
import { initializeAuth } from './auth.js';
@@ -108,13 +108,41 @@ const app = express();
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
/**
* CORS 中间件 - 根据配置动态处理跨域请求
*/
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key, anthropic-version');
const corsConfig = getCorsConfig();
// 如果 CORS 完全禁用,直接跳过
if (!corsConfig.enabled) {
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
return next();
}
const origin = req.headers.origin;
// 设置允许的方法和头
res.header('Access-Control-Allow-Methods', corsConfig.methods.join(', '));
res.header('Access-Control-Allow-Headers', corsConfig.headers.join(', '));
if (corsConfig.allowAll) {
// 允许所有来源(仅用于开发环境)
res.header('Access-Control-Allow-Origin', '*');
} else if (origin && corsConfig.origins.length > 0) {
// 白名单模式:验证请求来源
if (corsConfig.origins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
res.header('Vary', 'Origin');
}
// 不在白名单中的请求不设置 CORS 头,浏览器会拒绝
}
// 如果没有配置 origins 且不是 allowAll则不设置任何 CORS 头
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
return res.sendStatus(204);
}
next();
});