/** * Host头验证中间件 * 用于防止Host Header注入攻击 */ interface HostValidationResult { valid: boolean; error?: string; allowedHost?: string; } /** * 获取允许的Host列表 * 根据不同环境和端口配置返回相应的Host白名单 */ function getAllowedHosts(): string[] { // 基础IP地址 const baseIP = '10.79.97.17'; const testIP = '172.16.0.55'; const localhostIP = '127.0.0.1'; // 生产环境端口列表 const productionPorts = ['51703', '51704', '51705', '51706', '51707', '51708']; // 测试环境端口列表 const testPorts = ['5173', '5174', '5175', '5176', '5177', '5178']; const allowedHosts: string[] = []; // 添加基础IP(不带端口) allowedHosts.push(baseIP, testIP, 'localhost', localhostIP); // 添加生产环境的IP:端口组合 productionPorts.forEach(port => { allowedHosts.push(`${baseIP}:${port}`); }); // 添加测试环境的IP:端口组合 testPorts.forEach(port => { allowedHosts.push(`${testIP}:${port}`); allowedHosts.push(`localhost:${port}`); allowedHosts.push(`${localhostIP}:${port}`); }); // 开发环境额外允许的Host if (process.env.NODE_ENV === 'development') { allowedHosts.push('localhost:3000', '127.0.0.1:3000'); } return allowedHosts; } /** * 验证Host头是否在允许列表中 * @param request - Remix Request对象 * @returns 验证结果 */ export function validateHost(request: Request): HostValidationResult { const host = request.headers.get('host'); const referer = request.headers.get('referer'); const userAgent = request.headers.get('user-agent'); // 获取允许的Host列表 const allowedHosts = getAllowedHosts(); console.log('🔒 Host验证开始:', { host: host, referer: referer, userAgent: userAgent ? userAgent.substring(0, 50) + '...' : null, allowedHosts: allowedHosts }); // 1. 验证Host头是否存在 if (!host) { console.error('❌ Host头缺失'); return { valid: false, error: 'Missing Host header' }; } // 2. 验证Host头是否在允许列表中 if (!allowedHosts.includes(host)) { console.error('❌ Host头不在允许列表中:', { host: host, allowedHosts: allowedHosts }); return { valid: false, error: `Invalid Host header: ${host}` }; } // 3. 验证Referer头(如果存在) if (referer) { try { const refererUrl = new URL(referer); const refererHost = refererUrl.host; if (!allowedHosts.includes(refererHost)) { console.error('❌ Referer头不在允许列表中:', { referer: referer, refererHost: refererHost, allowedHosts: allowedHosts }); return { valid: false, error: `Invalid Referer header: ${refererHost}` }; } } catch (error) { console.error('❌ Referer头格式无效:', referer); return { valid: false, error: `Malformed Referer header: ${referer}` }; } } console.log('✅ Host验证通过:', host); return { valid: true, allowedHost: host }; } /** * 验证Origin头是否在允许列表中 * @param request - Remix Request对象 * @returns 验证结果 */ export function validateOrigin(request: Request): HostValidationResult { const origin = request.headers.get('origin'); if (!origin) { // Origin头不是必须的,某些请求(如直接访问)可能没有Origin头 return { valid: true }; } try { const originUrl = new URL(origin); const originHost = originUrl.host; const allowedHosts = getAllowedHosts(); if (!allowedHosts.includes(originHost)) { console.error('❌ Origin头不在允许列表中:', { origin: origin, originHost: originHost, allowedHosts: allowedHosts }); return { valid: false, error: `Invalid Origin header: ${originHost}` }; } console.log('✅ Origin验证通过:', origin); return { valid: true }; } catch (error) { console.error('❌ Origin头格式无效:', origin); return { valid: false, error: `Malformed Origin header: ${origin}` }; } } /** * 完整的请求验证 * 包括Host、Referer、Origin头的验证 * @param request - Remix Request对象 * @returns 验证结果 */ export function validateRequest(request: Request): HostValidationResult { // 1. 验证Host头 const hostValidation = validateHost(request); if (!hostValidation.valid) { return hostValidation; } // 2. 验证Origin头 const originValidation = validateOrigin(request); if (!originValidation.valid) { return originValidation; } return { valid: true }; } /** * 检查是否为受保护的路由 * 某些路由可能需要更严格的验证 * @param pathname - 请求路径 * @returns 是否为受保护路由 */ export function isProtectedRoute(pathname: string): boolean { const protectedRoutes = [ '/callback', '/api/oauth/token', '/api/oauth/userinfo', '/logout', '/admin' ]; return protectedRoutes.some(route => pathname.startsWith(route)); } /** * 记录安全事件 * @param event - 事件类型 * @param details - 事件详情 * @param request - 请求对象 */ export function logSecurityEvent( event: 'host_validation_failed' | 'origin_validation_failed' | 'referer_validation_failed', details: string, request: Request ) { const timestamp = new Date().toISOString(); const url = new URL(request.url); const clientIP = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'; console.error(`🚨 安全事件: ${event}`, { timestamp: timestamp, url: url.pathname + url.search, host: request.headers.get('host'), referer: request.headers.get('referer'), origin: request.headers.get('origin'), userAgent: request.headers.get('user-agent'), clientIP: clientIP, details: details }); // TODO: 这里可以添加更复杂的日志记录逻辑 // 比如写入数据库、发送告警邮件等 }