Django/DRF 中使用 HMAC 簽名確保伺服器間請求完整性
當伺服器與伺服器之間進行通訊時,如何確認「真的由我發出的請求」以及「途中沒有被篡改」? 本文將整理在 Django / DRF 環境中,利用 HMAC 簽名基礎認證 來確保伺服器間請求的完整性與可信度。
特別是,
- 這個技術的目的
- 能夠防止哪些事件
- 為什麼 不適用於客戶端-伺服器通訊(限制點)
- 在實際 Django/DRF 專案中,如何以
hmac_mixin.py+ CBV 來組成
將以程式碼範例說明。
HMAC 簽名認證是什麼?
HMAC(Hash-based Message Authentication Code) 是
「共用的秘密金鑰 + 訊息」產生 簽名值(signature) 的方式
。
- 兩台伺服器(A、B)都知道同一個 secret key。
-
A 伺服器向 B 伺服器發送
POST請求時: -
把請求 主體(body) 與 時間戳(timestamp) 組合,利用 HMAC 產生簽名值,
- 把這個簽名值放在
HTTP Header等位置一起傳送。 -
B 伺服器用同樣的 secret key 重新計算簽名,並:
-
若值相同 = 「這是 A 伺服器正常發出的請求」
- 若值不同 = 「中途被改動或有人偽造」
換句話說,HMAC 簽名同時檢查
- 完整性(integrity):內容是否被篡改
- 認證(authentication):是否由擁有秘密金鑰的一方發出
。
※ 前提:只能在伺服器間安全共享秘密金鑰。
這個技術的目的與能防止的事件
1. 防止請求主體被篡改
假設攻擊者截獲流量,
- 可能稍微改動請求主體,嘗試以其他帳號轉帳
- 或改變參數,嘗試以更大金額或不同選項進行操作
但接收伺服器會用「請求主體 + 時間戳」重新計算 HMAC 簽名;
- 若主體 哪怕一個字元 改變,簽名值也會完全不同,
- 簽名不符即拒絕請求。
2. 防止伺服器偽裝(spoofing)
即使攻擊者「假裝自己是伺服器」發送請求,
- 若不知道秘密金鑰,無法產生正確簽名
- 接收伺服器會判斷「簽名驗證失敗 → 不是真伺服器」
因此,伺服器之間可以以「只有知道秘密金鑰的人才是真」為前提進行通訊。
3. 防止重放攻擊(replay attack)
攻擊者可能把先前有效的請求複製並重複傳送。
通常 HMAC 簽名會同時包含:
- 時間戳(如
X-HMAC-Timestamp) - 或 nonce(一次性隨機值)
接收伺服器會檢查:
- 「請求時間太久遠」則拒絕
- 「已處理過的 nonce」再次收到則拒絕
這樣就能降低重放攻擊的風險。
※當然,僅靠 HMAC 不能解決所有問題,必須在 HTTPS(TLS)上使用,HMAC 只是額外的完整性/認證層。
HMAC 簽名的限制:為什麼不適用於客戶端(App、Web 前端)
有些情況下不應該使用 HMAC 簽名。
「在客戶端通訊中無法使用(因逆向工程會洩露金鑰)」
1. 客戶端程式碼無法隱藏秘密金鑰
- 移動 App、SPA 前端(JS)、桌面 App 等客戶端程式碼,最終都會被編譯或載入瀏覽器。
- 攻擊者可以反編譯或查看 JS,直接提取 secret key。
一旦金鑰洩露:
- 攻擊者能用該金鑰產生任意 HMAC 簽名
- 伺服器無法區分「真實客戶端」與「偽造請求」
因此,HMAC 的前提「金鑰只被兩台伺服器知道」被破壞。
結論:
- 伺服器 ↔ 伺服器(能安全管理金鑰的環境)
- 後端 ↔ 後端微服務
才適合使用;
- 移動 App、Web 前端等已部署的客戶端,應使用 JWT、OAuth2、Session 等常規 API 認證。
2. HMAC 不是加密(內容仍可見)
HMAC 只是一個「簽名」;
- 它不會加密請求主體,內容仍可被 TLS 之外的任何人看到。
因此:
- 敏感資料仍需透過 TLS(HTTPS)保護。
- HMAC 只是確認「內容未被篡改」與「發送者可信」的手段。
何時該使用 HMAC 基礎的伺服器間認證?(場景)
以下情境下,HMAC 簽名非常有用。
1. Django 應用 → 分離的 DRF 認證伺服器
- 多個服務共用一個 認證/會員伺服器(DRF)
- 主 Django 應用需要「登入驗證」或「發放 token」時,向 DRF 伺服器
POST。
此時:
- Django 應用用 HMAC 簽名的請求發送
- DRF 伺服器驗證簽名,確認是可信的 Django 應用發出的請求,然後回傳結果。
2. Django 應用 → AI 推論伺服器(DRF 或 FastAPI 等)
- Django 承擔前端/後端角色
- 重型 AI 推論工作交給獨立伺服器(DRF、FastAPI、Flask 等)
Django 會:
POST /v1/infer,傳送輸入文字、圖片 URL、選項等- 用 HMAC 簽名
AI 伺服器:
- 驗證 HMAC,若有效才使用 GPU 執行推論
這樣即使內部網路,也能減少「偽造請求」的風險,並為內部流量增添一層信任。
Django/DRF 實戰模式:hmac_mixin.py + CBV
實際專案中,以下結構非常乾淨。
- 專案(或 app)根目錄下放
hmac_mixin.py - 把「共用 HMAC 簽名 + POST 邏輯」抽成 Mixin
- 需要
POST的 CBV 直接繼承此 Mixin
以下示範。
1. hmac_mixin.py – 共用簽名 + POST 方法
# hmac_mixin.py
import hmac
import hashlib
import json
import time
import requests
from django.conf import settings
class HMACRequestMixin:
# 假設在 settings.py 定義
# HMAC_SECRET_KEY = "super-secret-key-from-env"
HMAC_SECRET_KEY = settings.HMAC_SECRET_KEY.encode("utf-8")
HMAC_HEADER_NAME = "X-HMAC-Signature"
HMAC_TIMESTAMP_HEADER = "X-HMAC-Timestamp"
HMAC_ALGORITHM = hashlib.sha256
def build_hmac_signature(self, body: bytes, timestamp: str) -> str:
"""利用 body 與 timestamp 產生 HMAC 簽名值。"""
message = timestamp.encode("utf-8") + b"." + body
digest = hmac.new(self.HMAC_SECRET_KEY, message, self.HMAC_ALGORITHM).hexdigest()
return digest
def post_with_hmac(self, url: str, payload: dict, timeout: int = 5):
"""向指定 URL 發送 HMAC 簽名的 POST 請求。"""
body = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
timestamp = str(int(time.time()))
signature = self.build_hmac_signature(body, timestamp)
headers = {
"Content-Type": "application/json",
self.HMAC_HEADER_NAME: signature,
self.HMAC_TIMESTAMP_HEADER: timestamp,
}
response = requests.post(url, data=body, headers=headers, timeout=timeout)
response.raise_for_status()
return response
現在任何 CBV,只要繼承 HMACRequestMixin,就能輕鬆發送簽名請求。
2. 送出端 Django CBV 範例
# views.py
from django.conf import settings
from django.http import JsonResponse
from django.views import View
from .hmac_mixin import HMACRequestMixin
class AIInferenceRequestView(HMACRequestMixin, View):
"""接收客戶端請求,轉發至 AI 伺服器並回傳結果。"""
def post(self, request, *args, **kwargs):
data = json.loads(request.body.decode("utf-8"))
payload = {
"text": data.get("text", ""),
"user_id": request.user.id if request.user.is_authenticated else None,
}
url = settings.AI_SERVER_URL # 例如 "https://ai-service.internal/v1/infer"
try:
ai_response = self.post_with_hmac(url, payload)
except requests.RequestException as e:
return JsonResponse({"detail": "AI server error", "error": str(e)}, status=502)
return JsonResponse(ai_response.json(), status=ai_response.status_code)
這樣,新增任何伺服器間呼叫,只需:
- 在 View 加入
HMACRequestMixin - 呼叫
self.post_with_hmac(url, payload)
Django/DRF 使用者強烈建議採用此模式,能讓 View 代碼更簡潔、重用性更高。
DRF 接收端:HMAC 簽名驗證 Authentication 類別範例
接收方(DRF 伺服器)需要驗證簽名。通常做法是寫一個自訂 Authentication 類別。
# authentication.py
import hmac
import hashlib
import time
from django.conf import settings
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class HMACSignatureAuthentication(BaseAuthentication):
SECRET_KEY = settings.HMAC_SECRET_KEY.encode("utf-8")
HMAC_HEADER_NAME = "X-HMAC-Signature"
HMAC_TIMESTAMP_HEADER = "X-HMAC-Timestamp"
ALGORITHM = hashlib.sha256
MAX_SKEW_SECONDS = 60 # 允許時間誤差(±60 秒)
def _build_signature(self, body: bytes, timestamp: str) -> str:
message = timestamp.encode("utf-8") + b"." + body
return hmac.new(self.SECRET_KEY, message, self.ALGORITHM).hexdigest()
def authenticate(self, request):
signature = request.headers.get(self.HMAC_HEADER_NAME)
timestamp = request.headers.get(self.HMAC_TIMESTAMP_HEADER)
if not signature or not timestamp:
raise AuthenticationFailed("Missing HMAC headers")
try:
ts = int(timestamp)
except ValueError:
raise AuthenticationFailed("Invalid timestamp")
now = int(time.time())
if abs(now - ts) > self.MAX_SKEW_SECONDS:
raise AuthenticationFailed("Request timestamp too old")
expected_signature = self._build_signature(request.body, timestamp)
if not hmac.compare_digest(signature, expected_signature):
raise AuthenticationFailed("Invalid HMAC signature")
# 伺服器間通訊,返回 (None, None) 或自訂服務帳號
return (None, None)
在 DRF View 中使用
# views.py (DRF 伺服器側)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from .authentication import HMACSignatureAuthentication
class AIInferenceView(APIView):
authentication_classes = [HMACSignatureAuthentication]
permission_classes = [AllowAny] # 只靠 HMAC 保護
def post(self, request, *args, **kwargs):
input_text = request.data.get("text", "")
result = {"answer": f"AI result for: {input_text}"}
return Response(result)
這樣即可完成:
- 發送端 Django:使用
HMACRequestMixin送出簽名請求 - 接收端 DRF:用
HMACSignatureAuthentication驗證簽名,驗證通過後執行業務
總結
- HMAC 簽名利用 共用秘密金鑰,同時驗證請求的 完整性 與 認證。
- 能有效防止:
- 請求主體被篡改
- 伺服器偽裝
- 重放攻擊(配合時間戳/nonce)
- 但 不適用於客戶端,因為金鑰無法隱藏,且 HMAC 不是加密。
- 在 Django/DRF 專案中,將簽名邏輯抽成
hmac_mixin.py,在 CBV 中繼承即可; - 接收端 DRF 可寫自訂 Authentication 類別驗證簽名,確保伺服器間通訊安全。

目前沒有評論。