apikey.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import time
  2. from typing import Optional, TypeVar, Generic, Type
  3. from fastapi import Request, Depends, HTTPException
  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.schema import ApiKeyPayload
  9. T = TypeVar("T")
  10. class TenantApiKeyAuth(Generic[T]):
  11. """
  12. 租户API Key认证
  13. """
  14. def __init__(self, data_type: Type[T], auto_error: bool = True):
  15. self.data_type: Type[T] = data_type
  16. self.auto_error: bool = auto_error
  17. async def __call__(
  18. self,
  19. request: Request,
  20. db: AsyncSession = Depends(db_getter)
  21. ) -> Optional[ApiKeyPayload[T]]:
  22. """
  23. 验证API Key
  24. """
  25. # 记录请求开始时间
  26. request.state.start_time = time.time()
  27. # 获取 Authorization 头
  28. authorization = request.headers.get("Authorization", None)
  29. if not authorization:
  30. if self.auto_error:
  31. raise HTTPException(status_code=401, detail="Authorization header required")
  32. return None
  33. # 获取 Signature 头
  34. signature = request.headers.get("Signature", None)
  35. if not signature:
  36. if self.auto_error:
  37. raise HTTPException(status_code=401, detail="Signature header required")
  38. return None
  39. # ========================= 验证 ApiKey =========================
  40. # 检查是否为ApiKey认证
  41. if not authorization.startswith("ApiKey "):
  42. if self.auto_error:
  43. raise HTTPException(status_code=401, detail="Invalid authorization format")
  44. return None
  45. # 提取API Key和签名
  46. api_key = authorization[7:]
  47. # 创建AuthSchema对象
  48. temp_auth = AuthSchema(user=None, db=db, tenant_id=1, check_data_scope=False)
  49. # 验证API Key
  50. api_key_obj = await TenantApiKeyService.validate_api_key(temp_auth, api_key)
  51. if not api_key_obj:
  52. # await TenantApiKeyService.log_api_call(
  53. # auth=temp_auth,
  54. # api_key_id=None,
  55. # tenant_id=temp_auth.tenant_id,
  56. # endpoint=str(request.url.path),
  57. # method=request.method,
  58. # request_ip=request.client.host if request.client else "unknown",
  59. # request_data=None,
  60. # response_code=401,
  61. # start_time=request.state.start_time,
  62. # )
  63. if self.auto_error:
  64. raise HTTPException(status_code=401, detail="Invalid API Key")
  65. return None
  66. # ========================= 验证 Signature =========================
  67. # 获取请求数据
  68. try:
  69. request_data = await request.json()
  70. except:
  71. request_data = {}
  72. if not TenantApiKeyService.verify_signature(api_key_obj.api_secret, request_data, signature):
  73. # await TenantApiKeyService.log_api_call(
  74. # auth=temp_auth,
  75. # api_key_id=api_key_obj.id,
  76. # tenant_id=api_key_obj.tenant_id,
  77. # endpoint=str(request.url.path),
  78. # method=request.method,
  79. # request_ip=request.client.host if request.client else "unknown",
  80. # request_data=request_data,
  81. # response_code=401,
  82. # start_time=request.state.start_time,
  83. # )
  84. if self.auto_error:
  85. raise HTTPException(status_code=401, detail="Invalid Signature")
  86. return None
  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. parsed_data = self.data_type(**request_data) if isinstance(request_data, dict) else request_data
  104. return ApiKeyPayload(
  105. api_key=api_key_obj.api_key,
  106. api_secret=api_key_obj.api_secret,
  107. tenant_id=api_key_obj.tenant_id,
  108. auth=AuthSchema(user=None, db=db, tenant_id=api_key_obj.tenant_id),
  109. data=parsed_data,
  110. )