在 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 签名的局限:为什么不适合客户端(App、Web 前端)



“在客户端与服务器通信时不适用(逆向工程导致密钥泄露)”

1. 客户端代码无法隐藏秘密密钥

  • 移动 App、SPA 前端(JS)、桌面 App 等客户端代码最终会被打包或在浏览器中运行
  • 攻击者可反编译或查看 JS,直接提取 secret key

一旦密钥泄露:

  • 攻击者可随意生成合法 HMAC,伪造请求
  • 服务器无法区分“真实客户端”与“伪造请求”

2. HMAC 不是加密(正文仍可被窃听)

HMAC 只验证“内容是否被篡改”,并不隐藏正文。

  • 仍需通过 TLS(HTTPS)保护敏感数据
  • 需要使用 JWT、OAuth2、Session 等常规 API 认证方式

结论:HMAC 适用于 服务器 ↔ 服务器后端 ↔ 后端微服务 的可信环境;不适用于已公开的客户端。


何时使用 HMAC 基于服务器间认证?(场景)

以下情况特别适合使用 HMAC 签名:

1. Django 应用 → 独立 DRF 认证服务器

  • 多个服务共用一个 认证/用户服务器(DRF)
  • 主 Django 应用需要向 DRF 服务器发起 POST(登录校验、令牌颁发等)

此时:

  • Django 通过 HMAC 签名请求
  • DRF 服务器验证签名后返回结果

2. Django 应用 → AI 推理服务器(DRF 或 FastAPI 等)

  • Django 负责前后端,AI 推理任务交给独立服务器
  • Django 向 AI 服务器发送 POST /v1/infer,携带文本、图片 URL、选项等
  • AI 服务器验证 HMAC 后才使用 GPU 进行推理

这样,即使内部网络,也能为服务间请求添加额外的可信层。


Django/DRF 实战模式:hmac_mixin.py + CBV

在实际项目中,以下结构非常简洁。

  1. 在项目(或 app)根目录放置 hmac_mixin.py
  2. 通用 HMAC 签名 + POST 逻辑 提取为 Mixin
  3. 需要 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,就能轻松发送 HMAC 签名请求。

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 服务器发送 HMAC 签名的 POST。
    """

    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) 即可。


DRF 接收端:HMAC 签名验证 Authentication 类示例

接收方(DRF 服务器)需要验证 HMAC。最常见做法是自定义 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")

        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 提取通用签名逻辑,并在 DRF 端通过自定义 Authentication 类完成验证。

image