- 新增 auth-middleware.js 验证客户端 API Key - 支持 Authorization: Bearer <key> 和 x-api-key 两种方式 - API Keys 只通过环境变量配置(安全最佳实践) - 公开路径: /, /health, /status - 可配置 /v1/models 是否需要认证 - 启动时输出认证状态日志 配置方式: AUTH_ENABLED=true API_KEYS=sk-key1,sk-key2 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
147 lines
3.7 KiB
JavaScript
147 lines
3.7 KiB
JavaScript
/**
|
||
* 请求认证中间件
|
||
* 验证客户端请求的 API Key,保护 API 端点
|
||
*/
|
||
|
||
import { getConfig } from './config.js';
|
||
import { logInfo, logError } from './logger.js';
|
||
|
||
// 不需要认证的路径(精确匹配)
|
||
const PUBLIC_PATHS = new Set([
|
||
'/',
|
||
'/health',
|
||
'/status'
|
||
]);
|
||
|
||
// 可配置是否公开的路径
|
||
const OPTIONAL_PUBLIC_PATHS = new Set([
|
||
'/v1/models'
|
||
]);
|
||
|
||
/**
|
||
* 获取认证配置
|
||
* API Keys 只从环境变量读取(安全考虑)
|
||
* enabled/public_models 可从 config.json 读取默认值,环境变量可覆盖
|
||
*/
|
||
export function getAuthConfig() {
|
||
const cfg = getConfig();
|
||
const configAuth = cfg.auth || {};
|
||
|
||
// 环境变量
|
||
const envEnabled = process.env.AUTH_ENABLED;
|
||
const envApiKeys = process.env.API_KEYS;
|
||
const envPublicModels = process.env.AUTH_PUBLIC_MODELS;
|
||
|
||
// 解析 enabled(环境变量 > config.json)
|
||
let enabled = configAuth.enabled ?? false;
|
||
if (envEnabled !== undefined) {
|
||
enabled = ['true', '1', 'yes'].includes(envEnabled.toLowerCase());
|
||
}
|
||
|
||
// API Keys 只从环境变量读取(敏感信息不应存储在配置文件中)
|
||
let apiKeys = [];
|
||
if (envApiKeys) {
|
||
apiKeys = envApiKeys.split(',').map(k => k.trim()).filter(k => k);
|
||
}
|
||
|
||
// 解析 public_models(环境变量 > config.json)
|
||
let publicModels = configAuth.public_models ?? true;
|
||
if (envPublicModels !== undefined) {
|
||
publicModels = ['true', '1', 'yes'].includes(envPublicModels.toLowerCase());
|
||
}
|
||
|
||
return {
|
||
enabled,
|
||
apiKeys: new Set(apiKeys),
|
||
publicModels
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 从请求头中提取 API Key
|
||
* 支持: Authorization: Bearer <key> 或 x-api-key: <key>
|
||
*/
|
||
function extractApiKey(req) {
|
||
// 优先检查 Authorization header
|
||
const authHeader = req.headers.authorization;
|
||
if (authHeader) {
|
||
if (authHeader.startsWith('Bearer ')) {
|
||
return authHeader.slice(7).trim();
|
||
}
|
||
// 也支持直接传 key(不带 Bearer 前缀)
|
||
return authHeader.trim();
|
||
}
|
||
|
||
// 其次检查 x-api-key header
|
||
const xApiKey = req.headers['x-api-key'];
|
||
if (xApiKey) {
|
||
return xApiKey.trim();
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 认证中间件
|
||
*/
|
||
export function authMiddleware(req, res, next) {
|
||
const authConfig = getAuthConfig();
|
||
|
||
// 如果认证未启用,直接放行
|
||
if (!authConfig.enabled) {
|
||
return next();
|
||
}
|
||
|
||
// 检查是否是公开路径
|
||
if (PUBLIC_PATHS.has(req.path)) {
|
||
return next();
|
||
}
|
||
|
||
// 检查可选公开路径
|
||
if (authConfig.publicModels && OPTIONAL_PUBLIC_PATHS.has(req.path)) {
|
||
return next();
|
||
}
|
||
|
||
// 检查 API Keys 是否配置
|
||
if (authConfig.apiKeys.size === 0) {
|
||
logError('Auth enabled but no API keys configured');
|
||
return res.status(500).json({
|
||
error: {
|
||
message: 'Server configuration error: authentication enabled but no API keys configured',
|
||
type: 'server_error',
|
||
code: 'auth_not_configured'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 提取并验证 API Key
|
||
const clientKey = extractApiKey(req);
|
||
|
||
if (!clientKey) {
|
||
logInfo(`Auth failed: No API key provided for ${req.method} ${req.path}`);
|
||
return res.status(401).json({
|
||
error: {
|
||
message: 'Missing API key. Please include your API key in the Authorization header using Bearer auth (Authorization: Bearer YOUR_API_KEY) or as x-api-key header.',
|
||
type: 'authentication_error',
|
||
code: 'missing_api_key'
|
||
}
|
||
});
|
||
}
|
||
|
||
if (!authConfig.apiKeys.has(clientKey)) {
|
||
logInfo(`Auth failed: Invalid API key for ${req.method} ${req.path}`);
|
||
return res.status(401).json({
|
||
error: {
|
||
message: 'Invalid API key provided.',
|
||
type: 'authentication_error',
|
||
code: 'invalid_api_key'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 认证通过
|
||
next();
|
||
}
|
||
|
||
export default authMiddleware;
|