Files
AI-Proxy-Worker/docs/Security.md
2025-08-17 19:59:10 +08:00

12 KiB
Raw Blame History

安全配置

🌍 Language / 语言

🇺🇸 English | 🇨🇳 中文

在生产环境中部署 AI Proxy Worker 的综合安全指南。遵循这些最佳实践,确保你的部署安全并防范常见威胁。

🔐 认证与授权

API 密钥安全

保护你的 AI 服务 API 密钥:

// ✅ 正确: 存储在 Cloudflare 密钥中
wrangler secret put DEEPSEEK_API_KEY

// ❌ 错误: 永远不要在源代码中硬编码
const API_KEY = "sk-1234567890abcdef"; // 永远不要这样做

代理密钥配置

设置安全的代理访问:

// 强代理密钥生成
const PROXY_KEY = crypto.randomUUID() + crypto.randomUUID();

// 设置为 Cloudflare 密钥
wrangler secret put PROXY_KEY

多层认证

实施分层安全:

async function authenticateRequest(request, env) {
  const authHeader = request.headers.get('Authorization');
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return { valid: false, error: '缺少或无效的授权头' };
  }
  
  const token = authHeader.substring(7);
  
  // 验证代理密钥
  if (token !== env.PROXY_KEY) {
    return { valid: false, error: '无效的代理密钥' };
  }
  
  return { valid: true };
}

🛡️ 输入验证与清理

请求验证

验证所有传入请求:

function validateChatRequest(data) {
  const errors = [];
  
  // 必需字段
  if (!data.messages || !Array.isArray(data.messages)) {
    errors.push('messages 必须是数组');
  }
  
  if (!data.model || typeof data.model !== 'string') {
    errors.push('model 必须是字符串');
  }
  
  // 消息验证
  data.messages?.forEach((msg, index) => {
    if (!msg.role || !['user', 'assistant', 'system'].includes(msg.role)) {
      errors.push(`messages[${index}].role 必须是 user、assistant 或 system`);
    }
    
    if (!msg.content || typeof msg.content !== 'string') {
      errors.push(`messages[${index}].content 必须是非空字符串`);
    }
    
    // 内容长度限制
    if (msg.content.length > 100000) {
      errors.push(`messages[${index}].content 超过最大长度`);
    }
  });
  
  // 参数验证
  if (data.temperature !== undefined) {
    if (typeof data.temperature !== 'number' || data.temperature < 0 || data.temperature > 2) {
      errors.push('temperature 必须是 0 到 2 之间的数字');
    }
  }
  
  return {
    valid: errors.length === 0,
    errors
  };
}

内容过滤

实施内容安全检查:

function containsUnsafeContent(text) {
  const unsafePatterns = [
    /\b(password|secret|key|token|密码|密钥|令牌)\s*[:=]\s*\w+/i,
    /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/, // 信用卡模式
    /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/ // 邮箱模式(如需要)
  ];
  
  return unsafePatterns.some(pattern => pattern.test(text));
}

// 在请求处理器中使用
if (containsUnsafeContent(message.content)) {
  return new Response(JSON.stringify({
    error: 'content_rejected',
    message: '请求包含潜在不安全内容'
  }), { status: 400 });
}

🚫 速率限制与 DDoS 防护

请求速率限制

实施速率限制以防止滥用:

class RateLimiter {
  constructor(maxRequests = 100, windowMs = 60000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = new Map();
  }
  
  isAllowed(clientId) {
    const now = Date.now();
    const windowStart = now - this.windowMs;
    
    // 清理旧条目
    for (const [id, timestamps] of this.requests.entries()) {
      const validTimestamps = timestamps.filter(ts => ts > windowStart);
      if (validTimestamps.length === 0) {
        this.requests.delete(id);
      } else {
        this.requests.set(id, validTimestamps);
      }
    }
    
    // 检查当前客户端
    const clientRequests = this.requests.get(clientId) || [];
    const recentRequests = clientRequests.filter(ts => ts > windowStart);
    
    if (recentRequests.length >= this.maxRequests) {
      return false;
    }
    
    // 添加当前请求
    recentRequests.push(now);
    this.requests.set(clientId, recentRequests);
    
    return true;
  }
  
  getRemainingRequests(clientId) {
    const clientRequests = this.requests.get(clientId) || [];
    const windowStart = Date.now() - this.windowMs;
    const recentRequests = clientRequests.filter(ts => ts > windowStart);
    
    return Math.max(0, this.maxRequests - recentRequests.length);
  }
}

// 使用方法
const rateLimiter = new RateLimiter(100, 60000); // 每分钟 100 个请求

async function handleRequest(request, env) {
  const clientId = getClientId(request);
  
  if (!rateLimiter.isAllowed(clientId)) {
    return new Response(JSON.stringify({
      error: 'rate_limit_exceeded',
      message: '请求过多。请稍后再试。',
      retryAfter: 60
    }), {
      status: 429,
      headers: {
        'Content-Type': 'application/json',
        'Retry-After': '60'
      }
    });
  }
  
  // 处理请求...
}

基于 IP 的防护

实施基于 IP 的安全措施:

function getClientIP(request) {
  return request.headers.get('CF-Connecting-IP') || 
         request.headers.get('X-Forwarded-For') || 
         'unknown';
}

const BLOCKED_IPS = new Set([
  '192.168.1.100',
  '10.0.0.50'
]);

function isBlockedIP(ip) {
  return BLOCKED_IPS.has(ip);
}

// 在请求处理器中使用
const clientIP = getClientIP(request);
if (isBlockedIP(clientIP)) {
  return new Response('访问被拒绝', { status: 403 });
}

🔒 数据保护

敏感数据处理

永远不要记录敏感信息:

function sanitizeForLogging(data) {
  const sanitized = { ...data };
  
  // 移除敏感字段
  delete sanitized.api_key;
  delete sanitized.authorization;
  delete sanitized.password;
  
  // 截断长内容
  if (sanitized.messages) {
    sanitized.messages = sanitized.messages.map(msg => ({
      ...msg,
      content: msg.content.length > 100 ? 
        msg.content.substring(0, 100) + '...[已截断]' : 
        msg.content
    }));
  }
  
  return sanitized;
}

// 使用方法
console.log('收到请求:', sanitizeForLogging(requestData));

响应清理

发送前清理响应:

function sanitizeResponse(response) {
  // 移除内部字段
  const sanitized = { ...response };
  delete sanitized.internal_id;
  delete sanitized.debug_info;
  delete sanitized.upstream_headers;
  
  return sanitized;
}

🌐 CORS 配置

安全 CORS 设置

正确配置 CORS

function setCORSHeaders(response, origin) {
  const allowedOrigins = [
    'https://yourdomain.com',
    'https://app.yourdomain.com',
    'https://localhost:3000' // 仅开发环境
  ];
  
  if (allowedOrigins.includes(origin)) {
    response.headers.set('Access-Control-Allow-Origin', origin);
  }
  
  response.headers.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  response.headers.set('Access-Control-Max-Age', '86400');
  
  return response;
}

// 处理预检请求
if (request.method === 'OPTIONS') {
  const origin = request.headers.get('Origin');
  const response = new Response(null, { status: 204 });
  return setCORSHeaders(response, origin);
}

📝 安全头

必要的安全头

为所有响应添加安全头:

function addSecurityHeaders(response) {
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-XSS-Protection', '1; mode=block');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set('Content-Security-Policy', "default-src 'none'");
  
  // 移除服务器信息
  response.headers.delete('Server');
  
  return response;
}

🔍 安全监控

安全事件日志

记录安全相关事件:

function logSecurityEvent(event, details) {
  console.warn('安全事件:', {
    event,
    timestamp: new Date().toISOString(),
    ...details
  });
  
  // 发送到安全监控服务
  if (env.SECURITY_WEBHOOK) {
    fetch(env.SECURITY_WEBHOOK, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        service: 'ai-proxy-worker',
        event,
        timestamp: new Date().toISOString(),
        ...details
      })
    }).catch(err => console.error('发送安全告警失败:', err));
  }
}

// 使用方法
logSecurityEvent('authentication_failure', {
  ip: getClientIP(request),
  userAgent: request.headers.get('User-Agent'),
  path: new URL(request.url).pathname
});

异常检测

检测异常模式:

class AnomalyDetector {
  constructor() {
    this.requestPatterns = new Map();
  }
  
  recordRequest(clientId, endpoint) {
    const key = `${clientId}:${endpoint}`;
    const now = Date.now();
    
    if (!this.requestPatterns.has(key)) {
      this.requestPatterns.set(key, []);
    }
    
    const requests = this.requestPatterns.get(key);
    requests.push(now);
    
    // 只保留最近的请求(最后一小时)
    const oneHourAgo = now - 3600000;
    const recentRequests = requests.filter(ts => ts > oneHourAgo);
    this.requestPatterns.set(key, recentRequests);
    
    // 检测异常
    if (recentRequests.length > 1000) { // 每小时超过 1000 个请求
      logSecurityEvent('high_frequency_requests', {
        clientId,
        endpoint,
        requestCount: recentRequests.length
      });
    }
  }
}

🛠️ 环境配置

生产环境变量

安全环境设置:

# 必需的密钥
wrangler secret put DEEPSEEK_API_KEY
wrangler secret put PROXY_KEY

# 可选的安全设置
wrangler secret put SECURITY_WEBHOOK
wrangler secret put RATE_LIMIT_MAX_REQUESTS
wrangler secret put ALLOWED_ORIGINS

配置验证

启动时验证环境:

function validateEnvironment(env) {
  const required = ['DEEPSEEK_API_KEY'];
  const missing = required.filter(key => !env[key]);
  
  if (missing.length > 0) {
    throw new Error(`缺少必需的环境变量: ${missing.join(', ')}`);
  }
  
  // 验证 API 密钥格式
  if (!env.DEEPSEEK_API_KEY.startsWith('sk-')) {
    console.warn('DEEPSEEK_API_KEY 可能无效 - 应以 sk- 开头');
  }
  
  return true;
}

🚨 事故响应

安全事故清单

检测到安全事故时:

  1. 立即响应

    • 识别事故范围
    • 如可能阻止恶意流量
    • 保存日志和证据
  2. 评估

    • 确定可能被访问的数据
    • 评估对用户和服务的影响
    • 检查持续威胁
  3. 控制

    • 轮换受损的 API 密钥
    • 更新安全规则
    • 如需要部署补丁
  4. 恢复

    • 恢复正常操作
    • 监控持续威胁
    • 验证安全措施
  5. 事后处理

    • 记录经验教训
    • 更新安全程序
    • 如需要通知利益相关者

紧急程序

// 紧急关闭能力
async function emergencyShutdown(reason) {
  logSecurityEvent('emergency_shutdown', { reason });
  
  // 返回维护模式响应
  return new Response(JSON.stringify({
    error: 'service_unavailable',
    message: '服务因维护暂时不可用',
    timestamp: new Date().toISOString()
  }), {
    status: 503,
    headers: {
      'Content-Type': 'application/json',
      'Retry-After': '3600'
    }
  });
}

📋 安全清单

部署前安全审查

  • 所有 API 密钥存储为密钥
  • 实施输入验证
  • 配置速率限制
  • 正确配置 CORS
  • 添加安全头
  • 清理日志
  • 错误消息不泄露信息
  • 更新依赖项
  • 启用安全监控

定期安全维护

  • 每月审查访问日志
  • 每季度更新依赖项
  • 每年轮换 API 密钥
  • 测试事故响应程序
  • 审查和更新安全策略

安全是一个持续的过程 🔒

定期审查和更新确保你的 AI Proxy Worker 对不断演变的威胁保持安全。