|
|
@@ -1,5 +1,9 @@
|
|
|
"""认证路由"""
|
|
|
|
|
|
+import hashlib
|
|
|
+import hmac
|
|
|
+import time
|
|
|
+
|
|
|
from fastapi import APIRouter, Request, HTTPException, Depends
|
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
|
|
|
|
@@ -9,23 +13,43 @@ router = APIRouter()
|
|
|
|
|
|
|
|
|
def verify_token(request: Request, credentials: HTTPAuthorizationCredentials | None = Depends(security)):
|
|
|
- """验证 Bearer Token"""
|
|
|
+ """验证 Bearer Token + 客户端签名(防非客户端请求)"""
|
|
|
config = request.app.state.config
|
|
|
if not credentials:
|
|
|
raise HTTPException(status_code=401, detail={"error": {"code": "unauthorized", "message": "缺少认证 token"}})
|
|
|
if credentials.credentials != config.token:
|
|
|
raise HTTPException(status_code=401, detail={"error": {"code": "unauthorized", "message": "Token 无效"}})
|
|
|
+
|
|
|
+ # 客户端请求签名验证(防止浏览器/curl直接调用)
|
|
|
+ req_ts = request.headers.get("X-Timestamp", "")
|
|
|
+ req_sig = request.headers.get("X-Signature", "")
|
|
|
+ if req_ts and req_sig:
|
|
|
+ try:
|
|
|
+ ts = int(req_ts)
|
|
|
+ if abs(time.time() - ts) > 300: # 5分钟窗口防重放
|
|
|
+ raise HTTPException(status_code=401, detail={
|
|
|
+ "error": {"code": "timestamp_expired", "message": "请求时间戳过期"}
|
|
|
+ })
|
|
|
+ path = request.url.path
|
|
|
+ expected = hmac.new(
|
|
|
+ config.token.encode(), f"{request.method}{path}{req_ts}".encode(), hashlib.sha256
|
|
|
+ ).hexdigest()
|
|
|
+ if not hmac.compare_digest(expected, req_sig):
|
|
|
+ raise HTTPException(status_code=403, detail={
|
|
|
+ "error": {"code": "invalid_signature", "message": "客户端签名无效"}
|
|
|
+ })
|
|
|
+ except (ValueError, TypeError):
|
|
|
+ raise HTTPException(status_code=401, detail={
|
|
|
+ "error": {"code": "bad_timestamp", "message": "时间戳格式错误"}
|
|
|
+ })
|
|
|
+
|
|
|
return True
|
|
|
|
|
|
|
|
|
@router.get("/health")
|
|
|
def health(request: Request):
|
|
|
"""健康检查(无需认证)"""
|
|
|
- config = request.app.state.config
|
|
|
- return {
|
|
|
- "status": "ok",
|
|
|
- "version": "1.0.0",
|
|
|
- }
|
|
|
+ return {"status": "ok", "version": "1.0.0"}
|
|
|
|
|
|
|
|
|
@router.post("/auth/verify")
|
|
|
@@ -34,5 +58,5 @@ def verify(request: Request, body: dict):
|
|
|
config = request.app.state.config
|
|
|
token = body.get("token", "")
|
|
|
if token == config.token:
|
|
|
- return {"valid": True}
|
|
|
+ return {"valid": True, "token": token}
|
|
|
raise HTTPException(status_code=401, detail={"error": {"code": "unauthorized", "message": "Token 无效"}})
|