FastAPI+Zoom Webhook本地正常Docker部署签名验证失败求助
问题:Docker运行FastAPI+Zoom Webhook时签名验证失败
搭建了FastAPI应用接收Zoom Webhook的会议/录制事件,本地通过ngrok转发运行正常,但构建为Docker镜像运行后出现signature rejected(签名验证失败)问题。已确认环境变量一致、依赖版本通过Poetry保持同步、容器时区与主机相同,问题仍未解决。
相关代码与配置
签名验证函数
def signature_verified(self, headers: dict, body: bytes): # 从请求头和体中提取必要组件 timestamp = headers['x-zm-request-timestamp'] if not timestamp: return False # 缺少时间戳,无法验证 decoded_body = body.decode('utf-8') message = f"v0:{timestamp}:{decoded_body}" print("Constructed Message:", message) # 获取Zoom Webhook密钥 secret_token = os.getenv('ZOOM_WEBHOOK_SECRET_TOKEN') if not secret_token: print("Secret token is missing.") return False # 创建HMAC SHA-256哈希 hash_for_verify = hmac.new(secret_token.encode(), message.encode(), hashlib.sha256).hexdigest() generated_signature = f"v0={hash_for_verify}" print("Computed HMAC:", hash_for_verify) human_readable = datetime.fromtimestamp(int(timestamp)) print("Timestamp from Zoom:", headers['x-zm-request-timestamp']) print("Human-readable timestamp:", human_readable) print("Generated Signature:", generated_signature) print("Expected Signature:", headers.get('x-zm-signature')) return headers.get('x-zm-signature') == generated_signature
Webhook路由代码
@app.post("/webhook") async def webhook(request: Request): client_host = request.client.host print(f"Request from IP: {client_host}") body = await request.body() headers = request.headers try: if zoom_client.signature_verified(headers, body): event_data = await request.json() if event_data["event"] == "endpoint.url_validation": logging.info("WEBHOOK: URL validation request received") return zoom_client.url_verification(event_data) elif event_data["event"] == "session.ended": topic = event_data["payload"]["object"]["topic"] id = event_data["payload"]["object"]["id"] logging.info(f"WEBHOOK: Session ended: {topic}({id}))") return {"message": "Received session.ended event."} else: logging.error("WEBHOOK: Unauthorized request to Webhook: Signature verified rejected") return {"message": "Unauthorized request to Zoom Webhook"}, 401 except Exception as e: logging.error(f"WEBHOOK: Error processing webhook: {e}") return {"message": "Error processing Zoom Webhook"}, 500
pyproject.toml
[tool.poetry] name = "zoomerizer" version = "0.1.0" description = "An app that summarizes the recordings of zoom meetings. It communicates with Zoom API." authors = ["<email>"] readme = "README.md" [tool.poetry.dependencies] python = "^3.11" fastapi = "^0.109.2" redis = "^5.0.1" types-redis = "^4.6.0.20240218" uvicorn = "^0.27.1" python-dotenv = "^1.0.1" requests = "^2.31.0" logging = "^0.4.9.6" coloredlogs = "^15.0.1" verboselogs = "^1.7" SQLAlchemy = "^2.0.27" langchain = "^0.1.8" openai = "^1.12.0" langchain-openai = "^0.0.8" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"
Dockerfile
# For more information, please refer to https://aka.ms/vscode-docker-python FROM python:3-slim WORKDIR /app ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 ENV POETRY_VERSION=1.8.2 RUN pip install "poetry==$POETRY_VERSION" COPY pyproject.toml poetry.lock* ./ RUN poetry config virtualenvs.create false \ && poetry install --no-dev --no-interaction --no-ansi EXPOSE 8000 COPY . /app RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser:appuser /app USER appuser CMD ["uvicorn", "zoomerizer.main:app", "--host", "0.0.0.0", "--port", "8000"]
运行命令与错误日志
运行命令:
docker run -e TZ=Europe/Berlin -p 8000:8000 --env-file .env zoomerizer
错误日志:
INFO: 192.168.65.1:64127 - "POST /webhook HTTP/1.1" 200 OK 2024-05-30 14:38:40,320 - ERROR - WEBHOOK: Unauthorized request to Webhook: Signature verified rejected
排查与解决方案
1. 确认请求体的一致性
Zoom签名基于原始请求体字节流计算,本地与Docker环境中请求体可能存在格式化差异(如换行符、空格、JSON缩进)。
- 打印本地和Docker环境下的
body原始字节内容(转换为十六进制或直接输出),对比是否完全一致。 - 确保在验证签名前,未对请求体进行任何修改(如二次解析、编码转换)。
2. 验证环境变量加载正确性
即使使用--env-file,Docker仍可能存在环境变量加载不全的情况:
- 在签名验证函数中添加
print(f"Secret token length: {len(secret_token)}"),对比本地与Docker中密钥长度是否一致(避免打印完整密钥)。 - 直接在容器内执行
echo $ZOOM_WEBHOOK_SECRET_TOKEN,确认变量值正确。
3. 统一Python版本
当前Docker镜像使用python:3-slim,默认版本可能低于pyproject.toml要求的3.11,导致字符串编码或HMAC计算差异:
- 修改Dockerfile基础镜像为
python:3.11-slim,确保Python版本与本地一致。
4. 兼容请求头大小写差异
部分服务器环境会将HTTP头转为小写,导致获取不到正确的Zoom头信息:
- 修改签名验证函数中的头获取逻辑,兼容大小写:
timestamp = headers.get('x-zm-request-timestamp') or headers.get('X-Zm-Request-Timestamp') expected_signature = headers.get('x-zm-signature') or headers.get('X-Zm-Signature')
5. 同步容器系统时间
Zoom签名验证要求时间戳在当前时间的5分钟范围内,容器时间偏差过大也会导致验证失败:
- 在容器内执行
date命令,对比主机时间是否一致。 - 可在Dockerfile中添加时间同步配置,或使用带NTP同步的基础镜像。
内容的提问来源于stack exchange,提问作者SarahData
相关产品推荐
相关产品推荐

