auth.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  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. return {"status": "ok", "version": "1.0.0"}
  46. @router.post("/auth/verify")
  47. def verify(request: Request, body: dict):
  48. """验证 token 有效性"""
  49. config = request.app.state.config
  50. token = body.get("token", "")
  51. if token == config.token:
  52. return {"valid": True, "token": token}
  53. raise HTTPException(status_code=401, detail={"error": {"code": "unauthorized", "message": "Token 无效"}})