| |
| """ |
| API 请求认证中间件/依赖项。 |
| 定义了用于验证 API 请求中提供的代理 Key 的函数。 |
| """ |
| import logging |
| from fastapi import Request, HTTPException, status |
| from typing import Dict, Any |
|
|
| |
| from app import config as app_config |
| |
| from app.core.context import store as context_store |
| |
| from app.core.database.utils import IS_MEMORY_DB |
| |
| from app.core.keys.manager import APIKeyManager |
|
|
| |
| logger = logging.getLogger('my_logger') |
|
|
| async def verify_proxy_key(request: Request) -> Dict[str, Any]: |
| """ |
| FastAPI 依赖项函数,用于验证 API 请求头中的 `Authorization: Bearer <token>`。 |
| |
| 验证逻辑根据 `KEY_STORAGE_MODE` 配置而不同: |
| - **内存模式 (`IS_MEMORY_DB=True`)**: 验证提供的 `<token>` 是否存在于环境变量 `PASSWORD` (即 `config.WEB_UI_PASSWORDS`) 定义的密码列表中。 |
| - **数据库模式 (`IS_MEMORY_DB=False`)**: 验证提供的 `<token>` 是否是数据库中存在且状态为激活 (`is_active=True`) 的 API Key。 |
| |
| 成功验证后,会从 `APIKeyManager` 获取该 Key 的配置信息,并将 Key 和配置信息作为字典返回。 |
| 同时,会将验证通过的 Key 存储在 `request.state.proxy_key` 中,供后续请求处理函数使用。 |
| |
| Args: |
| request (Request): FastAPI 请求对象,用于访问请求头和应用状态 (app.state)。 |
| |
| Returns: |
| Dict[str, Any]: 一个包含验证通过的 'key' (str) 和其对应 'config' (Dict[str, Any]) 的字典。 |
| |
| Raises: |
| HTTPException: |
| - 401 Unauthorized: 如果 Authorization 头缺失、格式错误、令牌无效或不匹配。 |
| - 403 Forbidden: 如果令牌在数据库模式下无效或非活动。 |
| - 503 Service Unavailable: 如果内存模式下未配置 `PASSWORD` 环境变量。 |
| """ |
| |
| auth_header: str | None = request.headers.get("Authorization") |
|
|
| |
| if not auth_header or not auth_header.startswith("Bearer "): |
| logger.warning("请求缺少有效的 Authorization Bearer header。") |
| |
| raise HTTPException( |
| status_code=status.HTTP_401_UNAUTHORIZED, |
| detail="未授权:缺少或无效的令牌格式。预期格式:'Bearer <token>'。", |
| headers={"WWW-Authenticate": "Bearer"}, |
| ) |
|
|
| |
| try: |
| |
| token = auth_header.split(" ")[1] |
| except IndexError: |
| logger.warning("Authorization Bearer header 格式无效,缺少 token。") |
| |
| raise HTTPException( |
| status_code=status.HTTP_401_UNAUTHORIZED, |
| detail="未授权:'Bearer ' 后的令牌格式无效。", |
| headers={"WWW-Authenticate": "Bearer"}, |
| ) |
|
|
| |
| if IS_MEMORY_DB: |
| |
| if not app_config.WEB_UI_PASSWORDS: |
| logger.error("API 认证失败(内存模式):未设置 WEB_UI_PASSWORDS (PASSWORD 环境变量)。") |
| |
| raise HTTPException( |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, |
| detail="服务配置错误:缺少 API 认证密码。", |
| ) |
| |
| if token not in app_config.WEB_UI_PASSWORDS: |
| logger.warning(f"API 认证失败(内存模式):提供的令牌与配置的密码不匹配。") |
| |
| raise HTTPException( |
| status_code=status.HTTP_401_UNAUTHORIZED, |
| detail="未授权:无效的令牌。", |
| headers={"WWW-Authenticate": "Bearer"}, |
| ) |
| |
| logger.debug(f"API 认证成功 (内存模式,使用 Key: {token[:8]}...).") |
| |
| request.state.proxy_key = token |
| |
| config_data = {'enable_context_completion': True} |
| return {"key": token, "config": config_data} |
|
|
| else: |
| |
| |
| |
| logger.warning("verify_proxy_key 正在调用 context_store.is_valid_proxy_key 进行验证,建议将验证逻辑移至 KeyManager。") |
| |
| if not await context_store.is_valid_proxy_key(token): |
| logger.warning(f"API 认证失败(数据库模式):提供的代理 Key 无效或非活动。Key: {token[:8]}...") |
| |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="未授权:无效或非活动的代理 API Key。", |
| ) |
|
|
| |
| try: |
| |
| key_manager_instance: APIKeyManager = request.app.state.key_manager |
| |
| config_data = key_manager_instance.get_key_config(token) |
| except AttributeError: |
| |
| logger.error("无法从 request.app.state 获取 key_manager 实例!") |
| raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="服务器内部错误:Key 管理器未正确初始化") |
|
|
| |
| if config_data is None: |
| |
| |
| logger.error(f"数据库模式下,Key {token[:8]}... 在数据库中有效,但在 KeyManager 中找不到配置。可能存在状态不一致。返回默认配置。") |
| config_data = {'enable_context_completion': True} |
|
|
| |
| logger.debug(f"API 认证成功 (数据库模式,使用代理 Key: {token[:8]}...),配置: {config_data}") |
| |
| request.state.proxy_key = token |
| |
| return {"key": token, "config": config_data} |
|
|