tenant_api_auth.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import time
  2. from typing import Optional, Dict, Any, Callable, Awaitable
  3. from fastapi import Request, Response, Depends, HTTPException, security
  4. from sqlalchemy.ext.asyncio import AsyncSession
  5. from app.core.dependencies import db_getter
  6. from app.api.v1.module_system.auth.schema import AuthSchema
  7. from app.plugin.module_payment.apikey.service import TenantApiKeyService
  8. from app.plugin.module_payment.apikey.model import TenantApiKeyModel
  9. class TenantApiKeyAuth:
  10. """
  11. 租户API Key认证
  12. """
  13. def __init__(self, auto_error: bool = True):
  14. self.auto_error = auto_error
  15. async def __call__(self, request: Request, db: AsyncSession = Depends(db_getter)) -> Optional[TenantApiKeyModel]:
  16. """
  17. 验证API Key
  18. """
  19. # 记录请求开始时间
  20. request.state.start_time = time.time()
  21. # 获取Authorization头
  22. authorization = request.headers.get("Authorization", None)
  23. if not authorization:
  24. if self.auto_error:
  25. raise HTTPException(status_code=401, detail="Authorization header required")
  26. return None
  27. # 检查是否为ApiKey认证
  28. if not authorization.startswith("ApiKey "):
  29. if self.auto_error:
  30. raise HTTPException(status_code=401, detail="Invalid authorization format")
  31. return None
  32. # 提取API Key和签名
  33. auth_str = authorization[7:]
  34. if ":" in auth_str:
  35. api_key, signature = auth_str.split(":", 1)
  36. else:
  37. api_key, signature = auth_str, None
  38. # 创建AuthSchema对象
  39. temp_auth = AuthSchema(
  40. user=None,
  41. check_data_scope=False,
  42. db=db,
  43. tenant_id=0
  44. )
  45. # 验证API Key
  46. api_key_obj = await TenantApiKeyService.validate_api_key(temp_auth, api_key)
  47. if not api_key_obj:
  48. # 记录失败日志
  49. await TenantApiKeyService.log_api_call(
  50. auth=temp_auth,
  51. api_key_id=None,
  52. tenant_id=0,
  53. endpoint=str(request.url.path),
  54. method=request.method,
  55. request_ip=request.client.host if request.client else "unknown",
  56. request_data=None,
  57. response_code=401,
  58. start_time=request.state.start_time,
  59. )
  60. if self.auto_error:
  61. raise HTTPException(status_code=401, detail="Invalid API Key")
  62. return None
  63. # 验证签名(如果提供)
  64. if signature:
  65. # 获取请求数据
  66. try:
  67. request_data = await request.json()
  68. except Exception:
  69. request_data = {}
  70. if not TenantApiKeyService.verify_signature(api_key_obj.api_secret, request_data, signature):
  71. # 记录失败日志
  72. await TenantApiKeyService.log_api_call(
  73. auth=temp_auth,
  74. api_key_id=api_key_obj.id,
  75. tenant_id=api_key_obj.tenant_id,
  76. endpoint=str(request.url.path),
  77. method=request.method,
  78. request_ip=request.client.host if request.client else "unknown",
  79. request_data=request_data,
  80. response_code=401,
  81. start_time=request.state.start_time,
  82. )
  83. if self.auto_error:
  84. raise HTTPException(status_code=401, detail="Invalid Signature")
  85. return None
  86. # 记录成功日志
  87. await TenantApiKeyService.log_api_call(
  88. auth=temp_auth,
  89. api_key_id=api_key_obj.id,
  90. tenant_id=api_key_obj.tenant_id,
  91. endpoint=str(request.url.path),
  92. method=request.method,
  93. request_ip=request.client.host if request.client else "unknown",
  94. request_data=None, # 避免记录敏感数据
  95. response_code=200,
  96. start_time=request.state.start_time,
  97. )
  98. # 更新最后使用时间
  99. await TenantApiKeyService.update_last_used(temp_auth, api_key_obj.id)
  100. # 将API Key对象存储到请求状态
  101. request.state.api_key = api_key_obj
  102. request.state.tenant_id = api_key_obj.tenant_id
  103. return api_key_obj
  104. async def get_tenant_from_api_key(
  105. request: Request,
  106. api_key: Optional[TenantApiKeyModel] = Depends(TenantApiKeyAuth(auto_error=False)),
  107. ) -> Optional[int]:
  108. """
  109. 从API Key获取租户ID
  110. """
  111. if api_key:
  112. return api_key.tenant_id
  113. return None