쿠키, 세션, 토큰 개념
HTTP 프로토콜의 무상태(stateless) 특성으로 인해 사용자 인증 정보 유지가 필요했습니다. 초기에는 쿠키(cookie)가 도입되었으며, 로그인 및 장바구니 기능 구현을 위해 세션(session)이 개발되었습니다. 세션은 서버 간 상태 공유 문제를 해결했지만 서버 장애 시 데이터 유실 취약점이 존재했습니다. 이를 개선하기 위해 토큰(token) 방식이 등장했습니다. 토큰은 암호화된 사용자 정보를 포함하며, 서버에서 검증 가능한 디지털 자격 증명 역할을 수행합니다.
JWT 동작 원리
JWT(JSON Web Token)는 헤더(Header), 페이로드(Payload), 서명(Signature)으로 구성된 문자열입니다. 형식: 헤더.페이로드.서명
헤더 구조
{
"typ": "JWT",
"alg": "HS256"
}
Base64 인코딩 처리됨
페이로드 구성
{
"exp": 1672531200,
"iat": 1672527600,
"user_id": "user123"
}
만료시간(exp), 발급시간(iat) 및 사용자 데이터 포함
서명 생성
HMAC-SHA256 알고리즘으로 헤더와 페이로드 조합에 서명 적용
Base64 인코딩/디코딩
import base64
import json
original_data = "인증정보"
json_data = json.dumps(original_data)
# 인코딩
encoded = base64.b64encode(json_data.encode('utf-8'))
print(f"인코딩 결과: {encoded.decode()}")
# 디코딩
decoded = base64.b64decode(encoded).decode('utf-8')
print(f"디코딩 결과: {json.loads(decoded)}")
DRF-JWT 기본 구현
Django REST Framework에서 JWT 인증 설정:
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView
urlpatterns = [
path('auth/login/', TokenObtainPairView.as_view()),
]
보호된 엔드포인트 설정:
# views.py
from rest_framework.views import APIView
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
class SecureAPI(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
return Response("인증된 사용자만 접근 가능")
응답 형식 커스터마이징
# settings.py
def custom_jwt_response(token, user=None, request=None):
return {
'status': 'success',
'user_id': user.id,
'access_token': str(token)
}
SIMPLE_JWT = {
'TOKEN_OBTAIN_SERIALIZER': 'app.utils.custom_jwt_response',
}
사용자 정의 모델과 토큰 발급
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomAuthAPI(APIView):
def post(self, request):
auth_data = request.data
try:
user = CustomUser.objects.get(
email=auth_data['email'],
password=auth_data['password']
)
token_serializer = TokenObtainPairSerializer()
token_data = token_serializer.get_token(user)
return Response({
'access': str(token_data.access_token),
'user': user.email
})
except CustomUser.DoesNotExist:
return Response({"error": "인증 실패"}, status=401)