const { Logger } = require('./logger'); const { ForbiddenError, ValidationError } = require('./errorHandler'); /** * 简单的API密钥认证中间件 */ const apiKeyAuth = (req, res, next) => { const apiKey = req.headers['x-api-key'] || req.query.apiKey; const validApiKey = process.env.API_KEY; // 如果没有配置API密钥,跳过验证 if (!validApiKey) { return next(); } if (!apiKey) { Logger.warn('缺少API密钥', { ip: req.ip, path: req.path, userAgent: req.get('User-Agent') }); return res.status(401).json({ success: false, error: '缺少API密钥', code: 'MISSING_API_KEY' }); } if (apiKey !== validApiKey) { Logger.warn('无效的API密钥', { ip: req.ip, path: req.path, providedKey: apiKey.substring(0, 8) + '...', userAgent: req.get('User-Agent') }); return res.status(401).json({ success: false, error: '无效的API密钥', code: 'INVALID_API_KEY' }); } next(); }; /** * 会话验证中间件 */ const sessionAuth = (req, res, next) => { const sessionId = req.headers['x-session-id']; // 如果没有会话ID,生成一个临时的 if (!sessionId) { req.sessionId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; Logger.debug('生成临时会话ID', { sessionId: req.sessionId }); } else { req.sessionId = sessionId; Logger.debug('使用现有会话ID', { sessionId }); } // 设置响应头 res.setHeader('x-session-id', req.sessionId); next(); }; /** * 基础权限检查中间件 */ const basicPermission = (requiredPermission) => { return (req, res, next) => { // 这里可以实现更复杂的权限逻辑 // 目前只是一个示例框架 const userPermissions = req.headers['x-user-permissions']?.split(',') || []; if (requiredPermission && !userPermissions.includes(requiredPermission)) { Logger.warn('权限不足', { ip: req.ip, path: req.path, requiredPermission, userPermissions }); throw new ForbiddenError('权限不足'); } next(); }; }; /** * 管理员权限检查 */ const adminOnly = (req, res, next) => { const isAdmin = req.headers['x-user-role'] === 'admin'; if (!isAdmin) { Logger.warn('非管理员尝试访问管理接口', { ip: req.ip, path: req.path, userRole: req.headers['x-user-role'] }); throw new ForbiddenError('需要管理员权限'); } next(); }; /** * 请求来源验证中间件 */ const originCheck = (req, res, next) => { const origin = req.headers.origin || req.headers.referer; const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || []; // 开发环境跳过来源检查 if (process.env.NODE_ENV === 'development') { return next(); } // 如果没有配置允许的来源,跳过检查 if (allowedOrigins.length === 0) { return next(); } if (!origin || !allowedOrigins.some(allowed => origin.includes(allowed))) { Logger.warn('不允许的请求来源', { origin, allowedOrigins, ip: req.ip, path: req.path }); return res.status(403).json({ success: false, error: '不允许的请求来源', code: 'FORBIDDEN_ORIGIN' }); } next(); }; /** * 用户代理检查中间件 */ const userAgentCheck = (req, res, next) => { const userAgent = req.get('User-Agent'); const blockedAgents = process.env.BLOCKED_USER_AGENTS?.split(',') || []; if (!userAgent) { Logger.warn('缺少User-Agent头部', { ip: req.ip, path: req.path }); // 可以选择是否阻止没有User-Agent的请求 if (process.env.REQUIRE_USER_AGENT === 'true') { return res.status(400).json({ success: false, error: '缺少User-Agent头部', code: 'MISSING_USER_AGENT' }); } } // 检查是否在黑名单中 if (userAgent && blockedAgents.some(blocked => userAgent.includes(blocked))) { Logger.warn('被阻止的User-Agent', { userAgent, ip: req.ip, path: req.path }); return res.status(403).json({ success: false, error: '不允许的客户端', code: 'BLOCKED_USER_AGENT' }); } next(); }; /** * 请求大小限制中间件 */ const requestSizeLimit = (maxSize = '10mb') => { return (req, res, next) => { const contentLength = parseInt(req.headers['content-length'] || '0'); const maxSizeBytes = parseSize(maxSize); if (contentLength > maxSizeBytes) { Logger.warn('请求体过大', { contentLength, maxSize: maxSizeBytes, ip: req.ip, path: req.path }); return res.status(413).json({ success: false, error: '请求体过大', code: 'PAYLOAD_TOO_LARGE', maxSize: maxSize }); } next(); }; }; /** * 解析大小字符串(如 '10mb', '1gb') */ function parseSize(sizeStr) { const units = { 'b': 1, 'kb': 1024, 'mb': 1024 * 1024, 'gb': 1024 * 1024 * 1024 }; const match = sizeStr.toLowerCase().match(/^(\d+(?:\.\d+)?)(b|kb|mb|gb)$/); if (!match) { throw new Error(`无效的大小格式: ${sizeStr}`); } const [, size, unit] = match; return parseFloat(size) * units[unit]; } /** * 内容类型验证中间件 */ const contentTypeCheck = (allowedTypes = ['application/json']) => { return (req, res, next) => { // 只对有请求体的方法进行检查 if (['POST', 'PUT', 'PATCH'].includes(req.method)) { const contentType = req.headers['content-type']; if (!contentType) { throw new ValidationError('缺少Content-Type头部'); } const isAllowed = allowedTypes.some(type => contentType.toLowerCase().includes(type.toLowerCase()) ); if (!isAllowed) { throw new ValidationError(`不支持的Content-Type: ${contentType}`); } } next(); }; }; module.exports = { apiKeyAuth, sessionAuth, basicPermission, adminOnly, originCheck, userAgentCheck, requestSizeLimit, contentTypeCheck };