기존 Django 인증 긴응에서는 HS256알고리즘이 적용된 서명 알고리즘을 사용하고 있었다.
istio에서 RequestAuthentication를 이용해서 토큰인증을 처리하게 구성하려고 방식을좀 변경해야 했다.

서명 알고리즘 방식
HS256 (HMAC with SHA-256)
- 대칭키 방식 → 하나의 비밀키(SECRET_KEY)로 토큰을 발급하고 검증.
- 서버가 여러 개라면 모든 서버에 동일한 비밀키를 공유해야 함.
- 관리가 간단하지만, 키 유출 시 보안 위험이 큼.
RS256 (RSA with SHA-256)
- 비대칭키 방식 → Private Key로 서명, Public Key로 검증.
- 토큰 발급 서버는 Private Key만 보관.
- 검증 서버들은 Public Key만 있으면 됨 → 여러 서비스/마이크로서비스 환경에 적합.
- Istio, Keycloak, Auth0 등 대부분의 IDP/게이트웨이가 RS256 + JWKS(JSON Web Key Set) 방식 권장.
Auth 애플리케이션 적용
Django settings.py 설정 변경
기존 코드
기존에 사용하던 코드는 HS256이 적용되어있다.
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True, # 사용한 토큰은 갱신하면 블랙리스트처리됨
}
변경 코드는 RS256으로 변경할 것이다. 빠져있던 ISSUER도 추가.
SIMPLE_JWT = {
"ALGORITHM": "RS256",
"SIGNING_KEY": PRIVATE_KEY,
"VERIFYING_KEY": PUBLIC_KEY,
"ISSUER": "msa-user", # 발급자 이름 검증단계에서 값 비교에 사용. HS256은 생략해도 동작.
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True, # 사용한 토큰은 갱신하면 블랙리스트처리됨
}
환경변수를 추가해서 환경변수 설정 여부에 따라 HS256과 RS256동적으로 적용되도록 적용.
DEBUG='1'
SQL_ENGINE='django.db.backends.mysql'
SQL_HOST="{DB_ADDRESS}"
SQL_USER="{DB_USER_NAME}"
SQL_PASSWORD="{DB_USER_PASSWORD}"
SQL_DATABASE="{DB_NAME}"
SQL_PORT='3306'
SECRET_KEY='django-insecure-#############'
ISTIO_JWT=1 # 이것 추가
settings.py 수정
암호화에 사용하는 key파일은 project/keys 폴더에 생성 및 적용하였음.
# 개인키 생성
openssl genrsa -out private.pem 2048
# 공개키 추출
openssl rsa -in private.pem -pubout -out public.pem
from datetime import timedelta
ISTIO_JWT = os.environ.get("ISTIO_JWT", "0") == "1"
if ISTIO_JWT:
# RS256 모드
# 운영환경에서 key파일은 POD mount로 적용하는게 안전
with open(BASE_DIR / "keys/private.pem", "r") as f:
PRIVATE_KEY = f.read()
with open(BASE_DIR / "keys/public.pem", "r") as f:
PUBLIC_KEY = f.read()
SIMPLE_JWT = {
"ALGORITHM": "RS256",
"SIGNING_KEY": PRIVATE_KEY,
"VERIFYING_KEY": PUBLIC_KEY,
"ISSUER": "msa-user",
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True, # 사용한 토큰은 갱신하면 블랙리스트처리됨
}
else:
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True, # 사용한 토큰은 갱신하면 블랙리스트처리됨
}
Django views 추가
jwks 공개키 경로 및 내용을 노출하기 위한 설정을 추가한다.
# users/views_jwks.py
from django.http import JsonResponse, HttpResponseNotFound
from django.conf import settings
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
def jwks_view(request):
if settings.SIMPLE_JWT["ALGORITHM"] != "RS256":
return HttpResponseNotFound("JWKS is only available in RS256 mode")
public_key = settings.SIMPLE_JWT["VERIFYING_KEY"]
key = serialization.load_pem_public_key(
public_key.encode(), backend=default_backend()
)
numbers = key.public_numbers()
e = numbers.e.to_bytes((numbers.e.bit_length() + 7) // 8, "big")
n = numbers.n.to_bytes((numbers.n.bit_length() + 7) // 8, "big")
jwk = {
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "msa-user-key",
"n": base64.urlsafe_b64encode(n).decode().rstrip("="),
"e": base64.urlsafe_b64encode(e).decode().rstrip("="),
}
return JsonResponse({"keys": [jwk]})
Django urls.py 수정
프로젝트 urls는 이미 적용되어 있으므로 app의 urls만 수정한다. ‘django-jwks’ 부분이 추가된 내용
from django.urls import path
from .views import RegisterView, MeView, CustomTokenObtainPairView, SSHKeyUploadView, SSHKeyInfoView, SSHKeyRetrieveView
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
from .views_jwks import jwks_view # django-jwks
urlpatterns = [
path('register/', RegisterView.as_view(), name='register'),
# path('login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('verify/', TokenVerifyView.as_view(), name='token_verify'),
path('me/', MeView.as_view(), name='me'),
path("ssh-key/", SSHKeyUploadView.as_view(), name="ssh_key_upload"),
path("ssh-key/info/", SSHKeyInfoView.as_view(), name="ssh_key_info"),
path("ssh-key/view/", SSHKeyRetrieveView.as_view(), name="ssh_key_retrieve"),
path(".well-known/jwks.json", jwks_view, name="jwks"), # django-jwks
]
Serializers.py 수정
istio jwt token 인증에는 payload에 iss와 sub가 반드시 필요함
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# ✅ JWT payload에 커스텀 정보 추가
token["name"] = user.name
token["grade"] = user.grade
token["email"] = user.email # 선택적으로 추가 가능
token["sub"] = user.email # 선택적으로 추가 가능
# Kong JWT 플러그인용 issuer 정보 추가
token["iss"] = "msa-user"
return token
...
분리된 서비스 구성 변경
환경변수는 동일하게 따라가고 settings.py에서 변경해주어야한다.
CRUD를 분리하여서 인증정보가 달라지면 인증되지 않아 등록되지 않는 현상 발생.
key파일들은 경로맞춰주고, SIMPLE_JWT 부분을 수정하여 적용한다.
ISTIO_JWT = os.environ.get("ISTIO_JWT", "0") == "1"
if ISTIO_JWT:
# RS256 모드
# 운영환경에서 key파일은 POD mount로 적용하는게 안전
with open(BASE_DIR / "keys/private.pem", "r") as f:
PRIVATE_KEY = f.read()
with open(BASE_DIR / "keys/public.pem", "r") as f:
PUBLIC_KEY = f.read()
SIMPLE_JWT = {
"ALGORITHM": "RS256",
"VERIFYING_KEY": PUBLIC_KEY,
"ISSUER": "msa-user",
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
}