| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- """
- 认证与用户校验中间件(ASGI)
- - 验证 Authorization: Bearer <token>
- - 校验用户/管理员存在性与用户卡密到期
- - 在 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()
|