Files
Aether/_deprecated_py_src/utils/auth_utils.py
fawney19 1d9c77522a refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
2026-04-03 16:26:16 +08:00

210 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
认证工具函数
提供统一的用户认证和授权功能
"""
from __future__ import annotations
import hashlib
from typing import Any
from fastapi import Depends, Header, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session
from src.core.logger import logger
from src.models.database import ManagementToken
from src.services.auth.service import AuthService
from src.utils.request_utils import get_client_ip
from ..core.exceptions import ForbiddenException
from ..database import get_db
from ..models.database import User, UserRole
security = HTTPBearer()
async def authenticate_user_from_bearer_token(
token: str,
db: Session,
request: Request | None = None,
) -> User:
if token.startswith(ManagementToken.TOKEN_PREFIX):
client_ip = get_client_ip(request) if request is not None else "unknown"
result = await AuthService.authenticate_management_token(db, token, client_ip)
if not result:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的Token")
user, management_token = result
if request is not None:
request.state.user_id = user.id
request.state.management_token_id = management_token.id
return user
# 验证Token格式和签名
try:
payload = await AuthService.verify_token(token, token_type="access")
except HTTPException as token_error:
token_fp = hashlib.sha256(token.encode()).hexdigest()[:12]
logger.error(
"Token验证失败: {}: {}, token_fp={}",
token_error.status_code,
token_error.detail,
token_fp,
)
raise
except Exception as token_error:
token_fp = hashlib.sha256(token.encode()).hexdigest()[:12]
logger.error("Token验证失败: {}, token_fp={}", token_error, token_fp)
raise ForbiddenException("无效的Token")
user_id = payload.get("user_id")
if not user_id:
logger.error("Token缺少user_id字段: payload={}", payload)
raise ForbiddenException("无效的认证凭据")
token_fp = hashlib.sha256(token.encode()).hexdigest()[:12]
if not isinstance(user_id, str):
logger.error("Token中user_id格式错误: {} - {}", type(user_id), user_id)
raise ForbiddenException("认证信息格式错误,请重新登录")
try:
from src.services.user.service import UserService
user = UserService.get_user(db, user_id)
except Exception as db_error:
logger.error("数据库查询失败: user_id={}, error={}", user_id, db_error)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="数据库查询失败,请稍后重试",
)
if not user:
logger.error("用户不存在: user_id={}", user_id)
raise ForbiddenException("用户不存在或已禁用")
if not user.is_active:
logger.error("用户已禁用: user_id={}", user_id)
raise ForbiddenException("用户不存在或已禁用")
if user.is_deleted:
logger.error("用户已删除: user_id={}", user_id)
raise ForbiddenException("用户不存在或已禁用")
if not AuthService.token_identity_matches_user(payload, user):
logger.error("Token身份校验失败: user_id={}, token_fp={}", user_id, token_fp)
raise ForbiddenException("身份验证失败")
if request is not None:
request.state.user_id = user.id
if hasattr(request.state, "management_token_id"):
request.state.management_token_id = None
return user
async def get_current_user(
request: Request,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db),
) -> User:
"""
获取当前登录用户
统一的认证依赖函数
Args:
credentials: Bearer token 凭据
db: 数据库会话
Returns:
User: 当前用户对象
Raises:
HTTPException: 认证失败时抛出
"""
try:
return await authenticate_user_from_bearer_token(credentials.credentials, db, request)
except HTTPException:
raise
except Exception as e:
logger.error("认证失败,未预期的错误: {}", e)
# 返回500而不是401避免触发前端的退出逻辑
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="认证服务暂时不可用"
)
async def get_current_user_from_header(
request: Request,
authorization: str | None = Header(None),
db: Session = Depends(get_db),
) -> User:
"""
从Header中获取当前用户兼容性函数
Args:
authorization: Authorization header
db: 数据库会话
Returns:
User: 当前用户对象
Raises:
HTTPException: 认证失败时抛出
"""
if not authorization or not authorization.startswith("Bearer "):
raise ForbiddenException("未提供认证令牌")
try:
return await authenticate_user_from_bearer_token(
authorization.replace("Bearer ", ""),
db,
request,
)
except HTTPException:
# 保持原始的HTTPException (包括401)
raise
except Exception as e:
logger.error(f"认证失败: {e}")
raise ForbiddenException("认证失败")
def require_admin(current_user: User = Depends(get_current_user)) -> User:
"""
要求管理员权限
Args:
current_user: 当前用户
Returns:
User: 管理员用户对象
Raises:
HTTPException: 非管理员时抛出403错误
"""
if current_user.role != UserRole.ADMIN:
raise ForbiddenException("需要管理员权限")
return current_user
def require_role(required_role: UserRole) -> Any:
"""
要求特定角色权限的装饰器工厂
Args:
required_role: 需要的用户角色
Returns:
依赖函数
"""
def check_role(current_user: User = Depends(get_current_user)) -> User:
if current_user.role != required_role:
raise ForbiddenException(f"需要{required_role.value}权限")
return current_user
return check_role