feat: 添加访问令牌管理功能并升级至 0.2.4

- 新增 Management Token(访问令牌)功能,支持创建、更新、删除和管理
- 前端添加访问令牌管理页面,支持普通用户和管理员
- 后端实现完整的令牌生命周期管理 API
- 添加数据库迁移脚本创建 management_tokens 表
- Nginx 配置添加 gzip 压缩,优化响应传输
- Dialog 组件添加 persistent 属性,防止意外关闭
- 为管理后台 API 添加详细的中文文档注释
- 简化多处类型注解,统一代码风格
This commit is contained in:
fawney19
2026-01-07 14:55:07 +08:00
parent f6a6410626
commit 0061fc04b7
59 changed files with 6265 additions and 648 deletions

View File

@@ -2,19 +2,23 @@ from __future__ import annotations
import time
from enum import Enum
from typing import Any, Optional, Tuple
from typing import TYPE_CHECKING, Any, Optional, Tuple
from fastapi import HTTPException, Request
from sqlalchemy.orm import Session
from src.config.settings import config
from src.core.enums import UserRole
from src.core.exceptions import QuotaExceededException
from src.core.logger import logger
from src.models.database import ApiKey, AuditEventType, User, UserRole
from src.models.database import ApiKey, AuditEventType, User
from src.services.auth.service import AuthService
from src.services.system.audit import AuditService
from src.services.usage.service import UsageService
if TYPE_CHECKING:
from src.models.database import ManagementToken
from .adapter import ApiAdapter, ApiMode
from .context import ApiRequestContext
@@ -47,17 +51,22 @@ class ApiRequestPipeline:
logger.debug(f"[Pipeline] Running with mode={mode}, adapter={adapter.__class__.__name__}, "
f"adapter.mode={adapter.mode}, path={http_request.url.path}")
if mode == ApiMode.ADMIN:
user = await self._authenticate_admin(http_request, db)
user, management_token = await self._authenticate_admin(http_request, db)
api_key = None
elif mode == ApiMode.USER:
user = await self._authenticate_user(http_request, db)
user, management_token = await self._authenticate_user(http_request, db)
api_key = None
elif mode == ApiMode.PUBLIC:
user = None
api_key = None
management_token = None
elif mode == ApiMode.MANAGEMENT:
user, management_token = await self._authenticate_management(http_request, db)
api_key = None
else:
logger.debug("[Pipeline] 调用 _authenticate_client")
user, api_key = self._authenticate_client(http_request, db, adapter)
management_token = None
logger.debug(f"[Pipeline] 认证完成 | user={user.username if user else None}")
raw_body = None
@@ -90,6 +99,9 @@ class ApiRequestPipeline:
api_format_hint=api_format_hint,
path_params=path_params,
)
# 存储 management_token 到 context用于权限检查
if management_token:
context.management_token = management_token
logger.debug(f"[Pipeline] Context构建完成 | adapter={adapter.name} | request_id={context.request_id}")
if mode != ApiMode.ADMIN and user:
@@ -177,12 +189,41 @@ class ApiRequestPipeline:
return user, api_key
async def _authenticate_admin(self, request: Request, db: Session) -> User:
async def _authenticate_admin(
self, request: Request, db: Session
) -> Tuple[User, Optional["ManagementToken"]]:
"""管理员认证,支持 JWT 和 Management Token 两种方式"""
from src.models.database import ManagementToken
from src.utils.request_utils import get_client_ip
authorization = request.headers.get("authorization")
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="缺少管理员凭证")
token = authorization[7:].strip()
# 检查是否为 Management Tokenae_ 前缀)
if token.startswith(ManagementToken.TOKEN_PREFIX):
client_ip = get_client_ip(request)
result = await self.auth_service.authenticate_management_token(db, token, client_ip)
if not result:
raise HTTPException(status_code=401, detail="无效或过期的 Management Token")
user, management_token = result
# 检查管理员权限
if user.role != UserRole.ADMIN:
logger.warning(f"非管理员尝试通过 Management Token 访问管理端点: {user.email}")
raise HTTPException(status_code=403, detail="需要管理员权限")
# 存储到 request.state
request.state.user_id = user.id
request.state.management_token_id = management_token.id
return user, management_token
# JWT 认证
try:
payload = await self.auth_service.verify_token(token, token_type="access")
except HTTPException:
@@ -200,16 +241,43 @@ class ApiRequestPipeline:
if not user or not user.is_active:
raise HTTPException(status_code=403, detail="用户不存在或已禁用")
request.state.user_id = user.id
return user
# 检查管理员权限
if user.role != UserRole.ADMIN:
logger.warning(f"非管理员尝试通过 JWT 访问管理端点: {user.email}")
raise HTTPException(status_code=403, detail="需要管理员权限")
request.state.user_id = user.id
return user, None
async def _authenticate_user(
self, request: Request, db: Session
) -> Tuple[User, Optional["ManagementToken"]]:
"""用户认证,支持 JWT 和 Management Token 两种方式"""
from src.models.database import ManagementToken
from src.utils.request_utils import get_client_ip
async def _authenticate_user(self, request: Request, db: Session) -> User:
"""JWT 认证普通用户(不要求管理员权限)"""
authorization = request.headers.get("authorization")
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="缺少用户凭证")
token = authorization[7:].strip()
# 检查是否为 Management Tokenae_ 前缀)
if token.startswith(ManagementToken.TOKEN_PREFIX):
client_ip = get_client_ip(request)
result = await self.auth_service.authenticate_management_token(db, token, client_ip)
if not result:
raise HTTPException(status_code=401, detail="无效或过期的 Management Token")
user, management_token = result
request.state.user_id = user.id
request.state.management_token_id = management_token.id
return user, management_token
# JWT 认证
try:
payload = await self.auth_service.verify_token(token, token_type="access")
except HTTPException:
@@ -222,13 +290,47 @@ class ApiRequestPipeline:
if not user_id:
raise HTTPException(status_code=401, detail="无效的用户令牌")
# 直接查询数据库,确保返回的是当前 Session 绑定的对象
user = db.query(User).filter(User.id == user_id).first()
if not user or not user.is_active:
raise HTTPException(status_code=403, detail="用户不存在或已禁用")
request.state.user_id = user.id
return user
return user, None
async def _authenticate_management(
self, request: Request, db: Session
) -> Tuple[User, "ManagementToken"]:
"""Management Token 认证"""
from src.models.database import ManagementToken
from src.utils.request_utils import get_client_ip
authorization = request.headers.get("authorization")
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="缺少 Management Token")
token = authorization[7:].strip()
# 检查是否为 Management Token 格式
if not token.startswith(ManagementToken.TOKEN_PREFIX):
raise HTTPException(
status_code=401,
detail=f"无效的 Token 格式,需要 Management Token ({ManagementToken.TOKEN_PREFIX}xxx)",
)
client_ip = get_client_ip(request)
result = await self.auth_service.authenticate_management_token(db, token, client_ip)
if not result:
raise HTTPException(status_code=401, detail="无效或过期的 Management Token")
user, management_token = result
# 存储到 request.state
request.state.user_id = user.id
request.state.management_token_id = management_token.id
return user, management_token
def _calculate_quota_remaining(self, user: Optional[User]) -> Optional[float]:
if not user: