feat: 增强 Gemini 认证支持与 URL 敏感信息脱敏

- 新增 extract_client_api_key_with_query 支持从 URL query 参数提取 API Key
- Gemini 格式遵循 Google SDK 行为:query 参数优先于 header
- 添加 redact_url_for_log 函数对日志中的敏感 URL 参数脱敏
- 上游请求始终使用 header 认证,清除 Gemini query 中的 key 参数
- 放宽 API Key 最小长度限制至 3 字符
- 完善认证方式相关代码注释
This commit is contained in:
fawney19
2026-01-22 19:11:38 +08:00
parent 1d27284575
commit ee5ba42b79
12 changed files with 176 additions and 40 deletions

View File

@@ -56,7 +56,7 @@ from src.models.database import (
User,
)
from src.services.cache.aware_scheduler import ProviderCandidate
from src.services.provider.transport import build_provider_url
from src.services.provider.transport import build_provider_url, redact_url_for_log
class ChatHandlerBase(BaseMessageHandler, ABC):
@@ -492,7 +492,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
# 同格式:按原逻辑做轻量清理(子类可覆盖以移除不需要的字段)
request_body = self.prepare_provider_request_body(request_body)
# 构建请求
# 构建请求(上游始终使用 header 认证,不跟随客户端的 query 方式)
provider_payload, provider_headers = self._request_builder.build(
request_body,
original_headers,
@@ -749,7 +749,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
# 同格式:按原逻辑做轻量清理(子类可覆盖以移除不需要的字段)
request_body = self.prepare_provider_request_body(request_body)
# 构建请求
# 构建请求(上游始终使用 header 认证,不跟随客户端的 query 方式)
provider_payload, provider_hdrs = self._request_builder.build(
request_body,
original_headers,
@@ -775,7 +775,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
f" [{self.request_id}] 发送非流式请求: Provider={provider.name}, "
f"模型={model} -> {mapped_model or '无映射'}"
)
logger.debug(f" [{self.request_id}] 请求URL: {url}")
logger.debug(f" [{self.request_id}] 请求URL: {redact_url_for_log(url)}")
logger.debug(
f" [{self.request_id}] 请求体stream字段: {provider_payload.get('stream', 'N/A')}"
)

View File

@@ -474,6 +474,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
# 使用 RequestBuilder 构建请求体和请求头
# 注意mapped_model 已经应用到 request_body这里不再传递
# 上游始终使用 header 认证,不跟随客户端的 query 方式
provider_payload, provider_headers = self._request_builder.build(
request_body,
original_headers,
@@ -1638,6 +1639,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
# 使用 RequestBuilder 构建请求体和请求头
# 注意mapped_model 已经应用到 request_body这里不再传递
# 上游始终使用 header 认证,不跟随客户端的 query 方式
provider_payload, provider_headers = self._request_builder.build(
request_body,
original_headers,

View File

@@ -72,6 +72,15 @@ class RequestBuilder(ABC):
"""
构建完整的请求(请求体 + 请求头)
Args:
original_body: 原始请求体
original_headers: 原始请求头
endpoint: 端点配置
key: Provider API Key
mapped_model: 映射后的模型名
is_stream: 是否为流式请求
extra_headers: 额外请求头
Returns:
Tuple[payload, headers]
"""
@@ -124,6 +133,12 @@ class PassthroughRequestBuilder(RequestBuilder):
) -> Dict[str, str]:
"""
透传请求头 - 清理敏感头部(黑名单),透传其他所有头部
Args:
original_headers: 原始请求头
endpoint: 端点配置
key: Provider API Key
extra_headers: 额外请求头
"""
from src.core.api_format import get_auth_config, resolve_api_format
@@ -136,6 +151,7 @@ class PassthroughRequestBuilder(RequestBuilder):
)
auth_value = f"Bearer {decrypted_key}" if auth_type == "bearer" else decrypted_key
# 认证头始终受保护,防止 header_rules 覆盖
protected_keys = {auth_header.lower(), "content-type"}
builder = HeaderBuilder()
@@ -147,7 +163,7 @@ class PassthroughRequestBuilder(RequestBuilder):
continue
builder.add(name, value)
# 3. 应用 endpoint 的请求头规则
# 3. 应用 endpoint 的请求头规则(认证头受保护,无法通过 rules 设置)
header_rules = getattr(endpoint, "header_rules", None)
if header_rules:
builder.apply_rules(header_rules, protected_keys)
@@ -156,7 +172,7 @@ class PassthroughRequestBuilder(RequestBuilder):
if extra_headers:
builder.add_many(extra_headers)
# 5. 设置认证头(最高优先级)
# 5. 设置认证头(最高优先级,上游始终使用 header 认证
builder.add(auth_header, auth_value)
# 6. 确保有 Content-Type