""" 认证与用户校验中间件(ASGI) - 验证 Authorization: Bearer - 校验用户/管理员存在性与用户卡密到期 - 在 scope 中注入 verified_user_id / verified_admin_id 供下游中间件使用 """ from typing import Callable from app.core.security import verify_token from app.database import SessionLocal from app.models.user import User from app.models.admin import Admin from app.utils.timezone import beijing_now from fastapi.responses import JSONResponse import json from datetime import datetime from app.core.logger import logger, get_logger_by_path EXEMPT_PATHS = [ "/admin/login", "/api/login", # 客户端不需要加密/鉴权的用户接口(明文调用时可免鉴权) "/api/user/login", "/api/user/register", "/api/user/unbind", "/api/user/recharge", # 公钥获取接口(需要时间戳验证) "/api/keys/public", "/health", "/docs", "/redoc", "/openapi.json", # 公共接口,可按需加入 "/api/health", # 健康检查接口 "/api/user/heartbeat", "/api/announcement", # 客户端公告查询接口 "/api/configs", # 客户端配置获取接口 ] class AuthValidationASGIMiddleware: def __init__(self, app): self.app = app async def __call__(self, scope, receive, send): # 仅处理 HTTP 请求 if scope.get("type") != "http": await self.app(scope, receive, send) return path = scope.get("path", "") method = scope.get("method", "").upper() # 根据路径选择对应的logger auth_logger = get_logger_by_path(path) # 跳过不需要验证的路径或 CORS 预检 if method == "OPTIONS" or any(path.startswith(p) for p in EXEMPT_PATHS): await self.app(scope, receive, send) return headers = {k.decode("utf-8"): v.decode("utf-8") for k, v in scope.get("headers", [])} auth_header = headers.get("authorization", "") or headers.get("Authorization", "") # 获取header中的用户信息用于组合校验 header_user_id = headers.get("x-user-id") or headers.get("X-User-Id") header_user_name = headers.get("x-user-name") or headers.get("X-User-Name") # 临时调试日志:记录客户端传来的鉴别头,帮助定位客户端是否传递了 X-User-Id/X-User-Name 或 Authorization try: auth_logger.debug(f"[AuthMiddleware][DEBUG HEADERS] path={path} method={method} X-User-Id={header_user_id} X-User-Name={header_user_name} AuthorizationPresent={bool(auth_header)}") except Exception: auth_logger.debug("[AuthMiddleware][DEBUG HEADERS] failed to read headers") if not auth_header or not auth_header.startswith("Bearer "): resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证信息缺失或无效"}) await resp(scope, receive, send) return token = auth_header[7:] payload = verify_token(token) if not payload: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证信息无效或已过期"}) await resp(scope, receive, send) return username = payload.get("sub") if not username: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证信息无效"}) await resp(scope, receive, send) return auth_logger.debug(f"[AuthMiddleware][DEBUG] Starting authentication for username: {username}, path: {path}") # 查询用户或管理员 db = SessionLocal() try: user = db.query(User).filter(User.username == username, User.is_deleted == 0).first() if user: auth_logger.debug(f"[AuthMiddleware][DEBUG] Found user: {user.username} (ID: {user.id})") # 检查状态与过期 if user.status != 1: resp = JSONResponse(status_code=403, content={"code": 403, "msg": "账号已被禁用"}) await resp(scope, receive, send) return if user.expire_time and user.expire_time < beijing_now(): resp = JSONResponse(status_code=403, content={"code": 403, "msg": "卡密已过期"}) await resp(scope, receive, send) return # 验证header中的用户信息是否匹配(组合校验) if header_user_id and header_user_name: try: header_user_id_int = int(header_user_id) if header_user_id_int != user.id or header_user_name != user.username: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息校验失败"}) await resp(scope, receive, send) return except ValueError: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息格式错误"}) await resp(scope, receive, send) return else: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "缺少用户信息"}) await resp(scope, receive, send) return scope["verified_user_id"] = user.id scope["verified_username"] = user.username scope["verified_is_admin"] = False auth_logger.debug(f"[AuthMiddleware][DEBUG] Set verified_user_id: {user.id} for user: {user.username}") await self.app(scope, receive, send) return admin = db.query(Admin).filter(Admin.username == username).first() if admin: auth_logger.debug(f"[AuthMiddleware][DEBUG] Found admin: {admin.username} (ID: {admin.id})") # 验证header中的用户信息是否匹配(组合校验) if header_user_id and header_user_name: try: header_user_id_int = int(header_user_id) if header_user_id_int != admin.id or header_user_name != admin.username: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息校验失败"}) await resp(scope, receive, send) return except ValueError: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息格式错误"}) await resp(scope, receive, send) return else: resp = JSONResponse(status_code=401, content={"code": 401, "msg": "缺少用户信息"}) await resp(scope, receive, send) return if admin.status != 1: resp = JSONResponse(status_code=403, content={"code": 403, "msg": "管理员账号已被禁用"}) await resp(scope, receive, send) return scope["verified_admin_id"] = admin.id scope["verified_username"] = admin.username scope["verified_is_admin"] = True auth_logger.debug(f"[AuthMiddleware][DEBUG] Set verified_admin_id: {admin.id} for admin: {admin.username}") await self.app(scope, receive, send) return # 未找到对应用户或管理员 auth_logger.debug(f"[AuthMiddleware][DEBUG] No user or admin found for username: {username}") resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证失败:用户不存在"}) await resp(scope, receive, send) return except Exception as e: auth_logger.exception(f"[AuthMiddleware][ERROR] Exception during authentication: {e}") resp = JSONResponse(status_code=500, content={"code": 500, "msg": "认证服务异常"}) await resp(scope, receive, send) return finally: db.close()