diff --git a/src/services/claudeRelayService.js b/src/services/claudeRelayService.js index 2fd853a7..9feeae0d 100644 --- a/src/services/claudeRelayService.js +++ b/src/services/claudeRelayService.js @@ -3,6 +3,7 @@ const zlib = require('zlib') const fs = require('fs') const path = require('path') const ProxyHelper = require('../utils/proxyHelper') +const { filterForClaude } = require('../utils/headerFilter') const claudeAccountService = require('./claudeAccountService') const unifiedClaudeScheduler = require('./unifiedClaudeScheduler') const sessionHelper = require('../utils/sessionHelper') @@ -877,62 +878,9 @@ class ClaudeRelayService { // 🔧 过滤客户端请求头 _filterClientHeaders(clientHeaders) { - // 需要移除的敏感 headers - const sensitiveHeaders = [ - 'content-type', - 'user-agent', - 'x-api-key', - 'authorization', - 'x-authorization', - 'host', - 'content-length', - 'connection', - 'proxy-authorization', - 'content-encoding', - 'transfer-encoding' - ] - - // 🆕 需要移除的浏览器相关 headers(避免CORS问题) - const browserHeaders = [ - 'origin', - 'referer', - 'sec-fetch-mode', - 'sec-fetch-site', - 'sec-fetch-dest', - 'sec-ch-ua', - 'sec-ch-ua-mobile', - 'sec-ch-ua-platform', - 'accept-language', - 'accept-encoding', - 'accept', - 'cache-control', - 'pragma', - 'anthropic-dangerous-direct-browser-access' // 这个头可能触发CORS检查 - ] - - // 应该保留的 headers(用于会话一致性和追踪) - const allowedHeaders = [ - 'x-request-id', - 'anthropic-version', // 保留API版本 - 'anthropic-beta' // 保留beta功能 - ] - - const filteredHeaders = {} - - // 转发客户端的非敏感 headers - Object.keys(clientHeaders || {}).forEach((key) => { - const lowerKey = key.toLowerCase() - // 如果在允许列表中,直接保留 - if (allowedHeaders.includes(lowerKey)) { - filteredHeaders[key] = clientHeaders[key] - } - // 如果不在敏感列表和浏览器列表中,也保留 - else if (!sensitiveHeaders.includes(lowerKey) && !browserHeaders.includes(lowerKey)) { - filteredHeaders[key] = clientHeaders[key] - } - }) - - return filteredHeaders + // 使用统一的 headerFilter 工具类 - 移除 CDN、浏览器和代理相关 headers + // 同时伪装成正常的直接客户端请求,避免触发上游 API 的安全检查 + return filterForClaude(clientHeaders) } _applyRequestIdentityTransform(body, headers, context = {}) { diff --git a/src/services/openaiResponsesRelayService.js b/src/services/openaiResponsesRelayService.js index e7b01d62..04a806b5 100644 --- a/src/services/openaiResponsesRelayService.js +++ b/src/services/openaiResponsesRelayService.js @@ -1,6 +1,7 @@ const axios = require('axios') const ProxyHelper = require('../utils/proxyHelper') const logger = require('../utils/logger') +const { filterForOpenAI } = require('../utils/headerFilter') const openaiResponsesAccountService = require('./openaiResponsesAccountService') const apiKeyService = require('./apiKeyService') const unifiedOpenAIScheduler = require('./unifiedOpenAIScheduler') @@ -73,9 +74,9 @@ class OpenAIResponsesRelayService { const targetUrl = `${fullAccount.baseApi}${req.path}` logger.info(`🎯 Forwarding to: ${targetUrl}`) - // 构建请求头 + // 构建请求头 - 使用统一的 headerFilter 移除 CDN headers const headers = { - ...this._filterRequestHeaders(req.headers), + ...filterForOpenAI(req.headers), Authorization: `Bearer ${fullAccount.apiKey}`, 'Content-Type': 'application/json' } @@ -810,29 +811,10 @@ class OpenAIResponsesRelayService { return { resetsInSeconds, errorData } } - // 过滤请求头 + // 过滤请求头 - 已迁移到 headerFilter 工具类 + // 此方法保留用于向后兼容,实际使用 filterForOpenAI() _filterRequestHeaders(headers) { - const filtered = {} - const skipHeaders = [ - 'host', - 'content-length', - 'authorization', - 'x-api-key', - 'x-cr-api-key', - 'connection', - 'upgrade', - 'sec-websocket-key', - 'sec-websocket-version', - 'sec-websocket-extensions' - ] - - for (const [key, value] of Object.entries(headers)) { - if (!skipHeaders.includes(key.toLowerCase())) { - filtered[key] = value - } - } - - return filtered + return filterForOpenAI(headers) } // 估算费用(简化版本,实际应该根据不同的定价模型) diff --git a/src/utils/headerFilter.js b/src/utils/headerFilter.js new file mode 100644 index 00000000..17ec1a38 --- /dev/null +++ b/src/utils/headerFilter.js @@ -0,0 +1,133 @@ +/** + * 统一的 CDN Headers 过滤列表 + * + * 用于各服务在原有过滤逻辑基础上,额外移除 Cloudflare CDN 和代理相关的 headers + * 避免触发上游 API(如 88code)的安全检查 + */ + +// Cloudflare CDN headers(橙色云代理模式会添加这些) +const cdnHeaders = [ + 'x-real-ip', + 'x-forwarded-for', + 'x-forwarded-proto', + 'x-forwarded-host', + 'x-forwarded-port', + 'x-accel-buffering', + 'cf-ray', + 'cf-connecting-ip', + 'cf-ipcountry', + 'cf-visitor', + 'cf-request-id', + 'cdn-loop', + 'true-client-ip' +] + +/** + * 为 OpenAI/Responses API 过滤 headers + * 在原有 skipHeaders 基础上添加 CDN headers + */ +function filterForOpenAI(headers) { + const skipHeaders = [ + 'host', + 'content-length', + 'authorization', + 'x-api-key', + 'x-cr-api-key', + 'connection', + 'upgrade', + 'sec-websocket-key', + 'sec-websocket-version', + 'sec-websocket-extensions', + ...cdnHeaders // 添加 CDN headers + ] + + const filtered = {} + for (const [key, value] of Object.entries(headers)) { + if (!skipHeaders.includes(key.toLowerCase())) { + filtered[key] = value + } + } + return filtered +} + +/** + * 为 Claude/Anthropic API 过滤 headers + * 在原有逻辑基础上添加 CDN headers 到敏感列表 + */ +function filterForClaude(headers) { + const sensitiveHeaders = [ + 'content-type', + 'user-agent', + 'x-api-key', + 'authorization', + 'x-authorization', + 'host', + 'content-length', + 'connection', + 'proxy-authorization', + 'content-encoding', + 'transfer-encoding', + ...cdnHeaders // 添加 CDN headers + ] + + const browserHeaders = [ + 'origin', + 'referer', + 'sec-fetch-mode', + 'sec-fetch-site', + 'sec-fetch-dest', + 'sec-ch-ua', + 'sec-ch-ua-mobile', + 'sec-ch-ua-platform', + 'accept-language', + 'accept-encoding', + 'accept', + 'cache-control', + 'pragma', + 'anthropic-dangerous-direct-browser-access' + ] + + const allowedHeaders = ['x-request-id', 'anthropic-version', 'anthropic-beta'] + + const filtered = {} + Object.keys(headers || {}).forEach((key) => { + const lowerKey = key.toLowerCase() + if (allowedHeaders.includes(lowerKey)) { + filtered[key] = headers[key] + } else if (!sensitiveHeaders.includes(lowerKey) && !browserHeaders.includes(lowerKey)) { + filtered[key] = headers[key] + } + }) + + return filtered +} + +/** + * 为 Gemini API 过滤 headers(如果需要转发客户端 headers 时使用) + * 目前 Gemini 服务不转发客户端 headers,仅提供此方法备用 + */ +function filterForGemini(headers) { + const skipHeaders = [ + 'host', + 'content-length', + 'authorization', + 'x-api-key', + 'connection', + ...cdnHeaders // 添加 CDN headers + ] + + const filtered = {} + for (const [key, value] of Object.entries(headers)) { + if (!skipHeaders.includes(key.toLowerCase())) { + filtered[key] = value + } + } + return filtered +} + +module.exports = { + cdnHeaders, + filterForOpenAI, + filterForClaude, + filterForGemini +}