auth.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. """
  2. 认证与用户校验中间件(ASGI)
  3. - 验证 Authorization: Bearer <token>
  4. - 校验用户/管理员存在性与用户卡密到期
  5. - 在 scope 中注入 verified_user_id / verified_admin_id 供下游中间件使用
  6. """
  7. from typing import Callable
  8. from app.core.security import verify_token
  9. from app.database import SessionLocal
  10. from app.models.user import User
  11. from app.models.admin import Admin
  12. from app.utils.timezone import beijing_now
  13. from fastapi.responses import JSONResponse
  14. import json
  15. from datetime import datetime
  16. from app.core.logger import logger
  17. EXEMPT_PATHS = [
  18. "/admin/login",
  19. "/api/login",
  20. "/health",
  21. "/docs",
  22. "/redoc",
  23. "/openapi.json",
  24. # 公共接口,可按需加入
  25. "/api/keys/public",
  26. "/api/user/heartbeat",
  27. ]
  28. class AuthValidationASGIMiddleware:
  29. def __init__(self, app):
  30. self.app = app
  31. async def __call__(self, scope, receive, send):
  32. # 仅处理 HTTP 请求
  33. if scope.get("type") != "http":
  34. await self.app(scope, receive, send)
  35. return
  36. path = scope.get("path", "")
  37. method = scope.get("method", "").upper()
  38. # 跳过不需要验证的路径或 CORS 预检
  39. if method == "OPTIONS" or any(path.startswith(p) for p in EXEMPT_PATHS):
  40. await self.app(scope, receive, send)
  41. return
  42. headers = {k.decode("utf-8"): v.decode("utf-8") for k, v in scope.get("headers", [])}
  43. auth_header = headers.get("authorization", "") or headers.get("Authorization", "")
  44. # 获取header中的用户信息用于组合校验
  45. header_user_id = headers.get("x-user-id") or headers.get("X-User-Id")
  46. header_user_name = headers.get("x-user-name") or headers.get("X-User-Name")
  47. # 临时调试日志:记录客户端传来的鉴别头,帮助定位客户端是否传递了 X-User-Id/X-User-Name 或 Authorization
  48. try:
  49. 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)}")
  50. except Exception:
  51. logger.debug("[AuthMiddleware][DEBUG HEADERS] failed to read headers")
  52. if not auth_header or not auth_header.startswith("Bearer "):
  53. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证信息缺失或无效"})
  54. await resp(scope, receive, send)
  55. return
  56. token = auth_header[7:]
  57. payload = verify_token(token)
  58. if not payload:
  59. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证信息无效或已过期"})
  60. await resp(scope, receive, send)
  61. return
  62. username = payload.get("sub")
  63. if not username:
  64. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证信息无效"})
  65. await resp(scope, receive, send)
  66. return
  67. logger.debug(f"[AuthMiddleware][DEBUG] Starting authentication for username: {username}, path: {path}")
  68. # 查询用户或管理员
  69. db = SessionLocal()
  70. try:
  71. user = db.query(User).filter(User.username == username, User.is_deleted == 0).first()
  72. if user:
  73. logger.debug(f"[AuthMiddleware][DEBUG] Found user: {user.username} (ID: {user.id})")
  74. # 检查状态与过期
  75. if user.status != 1:
  76. resp = JSONResponse(status_code=403, content={"code": 403, "msg": "账号已被禁用"})
  77. await resp(scope, receive, send)
  78. return
  79. if user.expire_time and user.expire_time < beijing_now():
  80. resp = JSONResponse(status_code=403, content={"code": 403, "msg": "卡密已过期"})
  81. await resp(scope, receive, send)
  82. return
  83. # 验证header中的用户信息是否匹配(组合校验)
  84. if header_user_id and header_user_name:
  85. try:
  86. header_user_id_int = int(header_user_id)
  87. if header_user_id_int != user.id or header_user_name != user.username:
  88. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息校验失败"})
  89. await resp(scope, receive, send)
  90. return
  91. except ValueError:
  92. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息格式错误"})
  93. await resp(scope, receive, send)
  94. return
  95. else:
  96. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "缺少用户信息"})
  97. await resp(scope, receive, send)
  98. return
  99. scope["verified_user_id"] = user.id
  100. scope["verified_username"] = user.username
  101. scope["verified_is_admin"] = False
  102. logger.debug(f"[AuthMiddleware][DEBUG] Set verified_user_id: {user.id} for user: {user.username}")
  103. await self.app(scope, receive, send)
  104. return
  105. admin = db.query(Admin).filter(Admin.username == username).first()
  106. if admin:
  107. logger.debug(f"[AuthMiddleware][DEBUG] Found admin: {admin.username} (ID: {admin.id})")
  108. # 验证header中的用户信息是否匹配(组合校验)
  109. if header_user_id and header_user_name:
  110. try:
  111. header_user_id_int = int(header_user_id)
  112. if header_user_id_int != admin.id or header_user_name != admin.username:
  113. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息校验失败"})
  114. await resp(scope, receive, send)
  115. return
  116. except ValueError:
  117. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "用户信息格式错误"})
  118. await resp(scope, receive, send)
  119. return
  120. else:
  121. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "缺少用户信息"})
  122. await resp(scope, receive, send)
  123. return
  124. if admin.status != 1:
  125. resp = JSONResponse(status_code=403, content={"code": 403, "msg": "管理员账号已被禁用"})
  126. await resp(scope, receive, send)
  127. return
  128. scope["verified_admin_id"] = admin.id
  129. scope["verified_username"] = admin.username
  130. scope["verified_is_admin"] = True
  131. logger.debug(f"[AuthMiddleware][DEBUG] Set verified_admin_id: {admin.id} for admin: {admin.username}")
  132. await self.app(scope, receive, send)
  133. return
  134. # 未找到对应用户或管理员
  135. logger.debug(f"[AuthMiddleware][DEBUG] No user or admin found for username: {username}")
  136. resp = JSONResponse(status_code=401, content={"code": 401, "msg": "认证失败:用户不存在"})
  137. await resp(scope, receive, send)
  138. return
  139. except Exception as e:
  140. logger.exception(f"[AuthMiddleware][ERROR] Exception during authentication: {e}")
  141. resp = JSONResponse(status_code=500, content={"code": 500, "msg": "认证服务异常"})
  142. await resp(scope, receive, send)
  143. return
  144. finally:
  145. db.close()