auth.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. """认证路由"""
  2. import hashlib
  3. import hmac
  4. import time
  5. from fastapi import APIRouter, Request, HTTPException, Depends
  6. from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
  7. security = HTTPBearer(auto_error=False)
  8. router = APIRouter()
  9. def verify_token(request: Request, credentials: HTTPAuthorizationCredentials | None = Depends(security)):
  10. """验证 Bearer Token + 客户端签名(防非客户端请求)"""
  11. config = request.app.state.config
  12. if not credentials:
  13. raise HTTPException(status_code=401, detail={"error": {"code": "unauthorized", "message": "缺少认证 token"}})
  14. if credentials.credentials != config.token:
  15. raise HTTPException(status_code=401, detail={"error": {"code": "unauthorized", "message": "Token 无效"}})
  16. # 客户端请求签名验证(所有认证请求必须带签名)
  17. req_ts = request.headers.get("X-Timestamp", "")
  18. req_sig = request.headers.get("X-Signature", "")
  19. if not req_ts or not req_sig:
  20. raise HTTPException(status_code=403, detail={
  21. "error": {"code": "missing_signature", "message": "缺少客户端签名,请使用 Flutter 客户端访问"}
  22. })
  23. try:
  24. ts = int(req_ts)
  25. if abs(time.time() - ts) > 300:
  26. raise HTTPException(status_code=401, detail={
  27. "error": {"code": "timestamp_expired", "message": "请求时间戳过期"}
  28. })
  29. path = request.url.path
  30. expected = hmac.new(
  31. config.token.encode(), f"{request.method}{path}{req_ts}".encode(), hashlib.sha256
  32. ).hexdigest()
  33. if not hmac.compare_digest(expected, req_sig):
  34. raise HTTPException(status_code=403, detail={
  35. "error": {"code": "invalid_signature", "message": "客户端签名无效"}
  36. })
  37. except (ValueError, TypeError):
  38. raise HTTPException(status_code=401, detail={
  39. "error": {"code": "bad_timestamp", "message": "时间戳格式错误"}
  40. })
  41. return True
  42. @router.get("/health")
  43. def health(request: Request):
  44. """健康检查(无需认证)"""
  45. from app.routers.import_ import _import_queue
  46. return {"status": "ok", "version": "1.0.0", "queue_depth": _import_queue.qsize()}
  47. @router.post("/auth/verify")
  48. def verify(request: Request, body: dict):
  49. """验证 token 有效性"""
  50. config = request.app.state.config
  51. token = body.get("token", "")
  52. if token == config.token:
  53. return {"valid": True, "token": token}
  54. raise HTTPException(status_code=401, detail={"error": {"code": "unauthorized", "message": "Token 无效"}})