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. 서버 위장(스푸핑) 방지
공격자가 “나도 저 서버인 척 해볼까?” 하고 요청을 보내도,
- 비밀 키를 모르면 올바른 서명값을 계산할 수 없습니다.
- 수신 서버는 “서명 검증 실패 → 이건 진짜 서버가 아니다”라고 판단합니다.
즉, 서버끼리 서로 “비밀 키를 아는 사람만 진짜” 라는 신뢰를 전제로 통신할 수 있습니다.
3. 리플레이 공격 방지(타임스탬프/nonce와 함께)
공격자가 이전에 유효했던 요청을 그대로 복사해서 반복 전송하는 것도 위험합니다.
그래서 보통 HMAC 서명에는:
- 타임스탬프 (
X-HMAC-Timestamp같은 헤더) - 또는 nonce(한 번 쓰고 버리는 랜덤 값)
을 같이 포함시키고, 수신 서버에서:
- “요청 시각이 너무 오래된 요청이면 거부”
- “한 번 처리한 nonce는 다시 받지 않음”
과 같은 정책을 적용하여 리플레이 공격도 줄일 수 있습니다.
※ 물론, HMAC만으로 모든 걸 해결할 수 있는 건 아니고, 반드시 HTTPS(TLS) 위에서 사용하는 것이 기본 전제입니다. HMAC은 추가적인 무결성/인증 계층입니다.
HMAC 서명의 한계: 왜 클라이언트(앱, 웹 프론트)에는 쓰면 안 되는가
HMAC 서명을 사용해서는 안되는 경우가 있습니다.
“클라이언트와의 통신에서는 사용할 수 없다. (리버스엔지니어링 때문에 key 노출)”
1. 클라이언트 코드에는 비밀 키를 숨길 수 없다
- 모바일 앱, SPA 프론트엔드(JS), 데스크톱 앱 등 클라이언트 코드에는 결국 빌드된 바이너리/자바스크립트 안에 키를 넣어야 합니다.
- 공격자가 앱을 디컴파일하거나, 브라우저에서 JS를 들여다보면 secret key를 추출할 수 있습니다.
한 번 키가 노출되면:
- 공격자는 그 키로 임의의 HMAC 서명을 만들 수 있고
- 서버 입장에서는 “이 요청이 진짜 클라이언트에서 온 건지, 공격자가 찍어낸 건지” 구별할 방법이 없습니다.
즉, HMAC의 전제(“키는 둘만 안다”)가 깨집니다.
그래서 HMAC 서명은:
- 서버 ↔ 서버 (비밀 키를 안전하게 관리 가능한 환경)
- 백엔드 ↔ 백엔드 마이크로서비스
같은 신뢰된 환경 사이에 쓰는 것이 맞고,
- 모바일 앱, 웹 프론트엔드 같은 배포된 클라이언트와의 통신에는 일반적인 API 인증(JWT, OAuth2, 세션 등)을 사용해야 합니다.
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)
이렇게 하면,
-
새로운 서버-서버 호출이 필요할 때마다
-
해당 View에
HMACRequestMixin을 상속하고 self.post_with_hmac(url, payload)만 호출하면 됩니다.
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)을 리턴해도 된다.
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):
# 여기까지 들어왔다는 건 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)에는 비밀 키를 숨길 수 없기 때문에 HMAC 서명을 클라이언트-서버 인증 용도로 쓰는 것은 부적절합니다.
-
실제 Django/DRF 프로젝트에서는
-
hmac_mixin.py에 공통 HMAC 서명 + POST 요청 메서드를 구현하고 - CBV에서 이 믹스인을 상속하여 간단히
post_with_hmac를 호출하는 패턴이 매우 편리합니다. - 수신 측 DRF 서버는 커스텀 Authentication 클래스로 HMAC 서명을 검증하여 서버-서버 통신을 안전하게 보호할 수 있습니다.

댓글이 없습니다.