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署名を計算 します。
- 本文が 1文字でも 変われば署名値も完全に変わるので
- 署名が合わなければリクエストを拒否できます。
2. サーバ偽装(スプーフィング)防止
攻撃者が "自分もそのサーバだと偽装してリクエストを送る" としても、
- 秘密鍵を知らなければ 正しい署名値を計算できません。
- 受信サーバは "署名検証失敗 → 本当のサーバではない" と判断します。
つまり、サーバ同士は "秘密鍵を知っている人だけが本物" という信頼を前提に通信できます。
3. リプレイ攻撃防止(タイムスタンプ/nonceと併用)
攻撃者が 以前有効だったリクエストをそのままコピーして再送 するリスクもあります。
そこで HMAC署名には通常、
- タイムスタンプ (
X-HMAC-Timestampなどのヘッダー) - nonce(使い捨てのランダム値)
を同時に含め、受信サーバで
- "リクエスト時刻が古すぎる場合は拒否"
- "一度処理した nonce は再度受け取らない"
といったポリシーを適用して リプレイ攻撃 も抑制します。
※ ただし、HMACだけで全てを解決できるわけではなく、必ず HTTPS(TLS) 上で使用 することが前提です。HMACは 追加の整合性/認証層 です。
HMAC署名の限界:なぜクライアント(アプリ、ウェブフロント)には使えないのか
HMAC署名を使ってはいけないケースがあります。
「クライアントとの通信では使えない。リバースエンジニアリングでキーが漏れる」
1. クライアントコードには秘密鍵を隠せない
- モバイルアプリ、SPAフロントエンド(JS)、デスクトップアプリなど クライアントコード には、結局ビルドされたバイナリ/JavaScript にキーを入れなければなりません。
- 攻撃者がアプリを逆コンパイルしたり、ブラウザで JS を覗き見ると secret key を抽出 できます。
一度キーが漏れると:
- 攻撃者はそのキーで 任意の HMAC署名 を作成でき、
- サーバ側では "このリクエストが本当のクライアントから来たか、攻撃者が作ったか" を区別できません。
つまり、HMACの前提("キーは二者だけが知っている")が崩れます。
したがって HMAC署名は:
- サーバ ↔ サーバ(秘密鍵を安全に管理できる環境)
- バックエンド ↔ バックエンドマイクロサービス
などの信頼できる環境で使うのが正しいです。
2. 暗号化ではない(本文はそのまま)
HMACは 「署名」 です。 本文を暗号化して隠すわけではなく、
- "本文がこのままであるか"
- "誰が変更していないか"
を確認するためのものです。
したがって:
- 機密データは依然として TLS(HTTPS) で保護 する必要があります。
- HMAC が本文を隠すわけではないことを理解してください。
いつこの HMACベースのサーバ間認証を使うべきか?(シナリオ)
以下のような状況で HMAC署名は有効です。
1. Django アプリケーション → 別の DRF 認証サーバ
- 複数サービスが共通で使う 認証/会員サーバ が DRF で別に設置されている構成
- メインの Django アプリは "ログイン検証"、"トークン発行検証" などで DRF サーバへ
POSTリクエスト
このとき:
- Django アプリが DRF サーバへ HMAC署名付きリクエスト を送り、
- DRF サーバは "これは信頼できる Django アプリからのリクエスト" と検証して結果を返します。
2. Django アプリケーション → AI 推論サーバ(DRF または FastAPI 等)
- Django がフロント/バックエンドを担い、
- 重い AI 推論は 別サーバ(DRF、FastAPI、Flask 等)に任せる構成
Django が AI サーバへ:
POST /v1/inferを送り、- 入力テキスト、画像URL、オプションなどを送信し、
- このリクエストを HMAC で署名
AI サーバは:
- 受信したリクエストの HMAC署名を検証し、
- 有効なら GPU リソースを使って推論実行
こうすることで:
- 内部ネットワークでも "サービス間の偽造リクエスト" を減らせ、
- 内部トラフィックに追加の信頼層を設けられます。
Django/DRFで使う実践パターン:hmac_mixin.py + CBV
実際のプロジェクトでは次の構成がとてもきれいです。
- プロジェクト(またはアプリ)直下に
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 リクエストを送る。
"""
# JSON シリアライズ時は sort_keys + separators で両側フォーマットを揃えると安全。
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 の例
例:Django が AI 推論サーバ(DRF)へリクエストを送るビュー
# 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):
# クライアントリクエストを基に payload を構築
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,
}
# AI サーバ URL(settings で管理)
url = settings.AI_SERVER_URL # 例: "https://ai-service.internal/v1/infer"
# Mixin が提供する post_with_hmac を使用
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)
# AI サーバの JSON 応答をそのままクライアントへ返す
return JsonResponse(ai_response.json(), status=ai_response.status_code)
これで、
- 新しいサーバ間呼び出しが必要になったら
- そのビューに
HMACRequestMixinを継承し self.post_with_hmac(url, payload)だけ呼び出せば OK
Django/DRF を使っているならこの方法を強く推奨します。ビューコードが非常に簡潔になり、再利用性が高まります。
DRF 受信側:HMAC署名検証 Authentication クラスの例
次に、リクエストを受ける側(DRF サーバ)では 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 # 許容時間ズレ(例: ±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")
# サーバ間通信なら別の 'service アカウント' 概念を返すか、
# (user, auth) タプルの代わりに (None, None) を返しても OK。
return (None, None)
DRF ビューへの適用
# 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):
# ここまで来たら HMAC 検証に成功
input_text = request.data.get("text", "")
# ここで実際の AI 推論を実行
result = {"answer": f"AI result for: {input_text}"}
return Response(result)
こう構成すると:
- 送信側 Django:
HMACRequestMixinで簡単に署名付きリクエスト送信 - 受信側 DRF:
HMACSignatureAuthenticationで署名検証
という明確な構造が完成します。
まとめ
- HMAC署名は サーバ間で共有された秘密鍵 を使い、 リクエストの 整合性(改ざん防止) と 認証(誰が送ったか) を検証する手法です。
- この手法は
- リクエスト本文の改ざん
- サーバ偽装(スプーフィング)
- リプレイ攻撃(タイムスタンプ/nonce と併用) などを減らすのに効果的です。
- ただし、
- クライアントコード(アプリ、JS)には秘密鍵を隠せない ため、 クライアント-サーバ認証には不適切です。
- 実際の Django/DRF プロジェクトでは
hmac_mixin.pyに 共通 HMAC署名 + POSTリクエストメソッド を実装し、- CBV でこのミックスインを継承して簡単に
post_with_hmacを呼び出せます。 - 受信側 DRF は カスタム Authentication クラス で HMAC署名を検証し、 サーバ間通信を安全に保護できます。

コメントはありません。