Защита целостности запросов между серверами в Django/DRF с помощью HMAC‑подписей
Когда два сервера обмениваются данными, как убедиться, что запрос действительно пришёл от нужного сервера и не был изменён в пути? В этой статье рассматривается, как в среде Django / DRF использовать подпись на основе HMAC для обеспечения целостности и доверия между серверами.
Особенно:
- Зачем нужна эта техника
- Какие угрозы она предотвращает
- Почему она не подходит для клиент‑серверной коммуникации (ограничения)
- Как реализовать
hmac_mixin.py+ CBV в реальном проекте
Что такое HMAC‑подпись?
HMAC (Hash-based Message Authentication Code) – это способ создания подписи из «секретного ключа + сообщения».
- Два сервера (A, B) знают один и тот же секретный ключ.
- Сервер A, отправляя
POSTк серверу B: - Собирает тело запроса и таймстамп
- Создаёт подпись HMAC
- Прикладывает подпись к заголовку
HTTP Headerи отправляет. - Сервер B, получив запрос, делает то же самое с тем же ключом и сравнивает подписи:
- Если совпадают – запрос считается надёжным
- Если нет – отклоняется.
Таким образом, HMAC‑подпись проверяет одновременно:
- Целостность – содержимое не изменилось
- Аутентификацию – только владелец ключа мог создать подпись
※ Предполагается, что секретный ключ может быть безопасно обменян между серверами.
Цели и предотвращаемые угрозы
1. Защита от подмены тела запроса
Если злоумышленник перехватывает трафик, он может изменить тело запроса, например, изменить сумму перевода. Сервер, пересчитав подпись, обнаружит изменение и отклонит запрос.
2. Защита от подмены сервера (spoofing)
Без знания секретного ключа злоумышленник не сможет сформировать корректную подпись. Сервер отклонит запрос, считая его фальшивым.
3. Защита от replay‑атак
Подпись обычно сопровождается таймстампом или nonce. Сервер отклоняет запросы, которые слишком старые или уже использованы.
※ HMAC – дополнительный слой над HTTPS/TLS; не заменяет шифрование.
Почему HMAC не подходит для клиент‑серверных взаимодействий
1. Ключ не может быть скрыт в клиентском коде
Мобильные приложения, SPA‑frontend, десктопные клиенты – все они вынуждены хранить ключ в исполняемом коде. При реверс‑инжиниринге ключ может быть извлечён, и злоумышленник сможет генерировать валидные подписи.
2. HMAC не шифрует тело
Подпись лишь подтверждает неизменность, но не скрывает данные. Чувствительные данные всё равно защищаются TLS.
Итого: HMAC‑подпись предназначена для сервер ↔ сервер и backend‑backend микросервисов, но не для клиентских приложений.
Когда стоит использовать HMAC‑подпись? (Сценарии)
1. Django‑приложение → отдельный DRF‑сервер аутентификации
Много сервисов используют общий сервер аутентификации. Основное приложение отправляет POST‑запросы с HMAC‑подписью, а DRF‑сервер проверяет и возвращает токены.
2. Django‑приложение → AI‑сервер (DRF, FastAPI и т.д.)
Джанго обрабатывает фронтенд, а тяжёлые AI‑инференсы делегируются отдельному серверу. Запросы к AI‑серверу подписываются HMAC, чтобы предотвратить подделку.
Практический паттерн в Django/DRF: hmac_mixin.py + CBV
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:
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:
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):
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
2. Пример 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):
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
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)
3. Приёмник HMAC в 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
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")
return (None, None)
# 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]
def post(self, request, *args, **kwargs):
input_text = request.data.get("text", "")
result = {"answer": f"AI result for: {input_text}"}
return Response(result)
Итоги
- HMAC‑подпись обеспечивает целостность и аутентификацию запросов между серверами.
- Эффективно защищает от:
- Подмены тела запроса
- Подмены сервера
- Replay‑атак (с таймстампом/nonce)
- Не подходит для клиент‑серверных взаимодействий из‑за раскрытия ключа и отсутствия шифрования.
- В Django/DRF удобно реализовать через
hmac_mixin.pyи CBV, а на стороне DRF – через кастомныйAuthentication.

Комментариев нет.