- Json Web Token
- jwt.io(Decode 사이트)
- Login, 유저인증 ...
- Session 기반이 아닌 Token 기반 인증 방식
유저 정보를 매번 가져오는것이 Session 형태, key:value 형태
{
"key" : 1
}
- 헤더(알고리즘과 타입), PAYLOAD(데이터), SIGNATURE(변조됬는지 확인)
비교 항목 | JWT (JSON Web Token) | Session (Django Session) |
---|---|---|
서버 부담 | 낮음 (Stateless) | 높음 (Stateful) |
보안 | 서명과 암호화 필요, 탈취 시 위험 | 서버에서 관리하므로 안전함 |
속도 | 빠름 (토큰만 검증) | 느림 (DB 조회 필요) |
저장 위치 | 클라이언트 (localStorage, sessionStorage, HTTP Only 쿠키) | 서버 (DB, Redis, 캐시 등) |
CSRF 위험 | ❌ 없음 (헤더 기반 인증) | ✅ 있음 (쿠키 기반 인증) |
사용 예시 | REST API, 모바일 앱 인증 | Django 기본 인증 |
사용 예시 | 추천 인증 방식 |
---|---|
REST API (백엔드-프론트엔드 분리) | ✅ JWT (토큰 기반 인증) |
SPA (Single Page Application) | ✅ JWT + HTTP Only 쿠키 (보안 강화) |
전통적인 웹 애플리케이션 (Django 기본 인증) | ✅ Session 기반 인증 |
모바일 앱 (iOS/Android) | ✅ JWT (토큰을 로컬에 저장) |
보안이 중요한 서비스 (은행, 금융 등) | ✅ Session + 추가 보안 (2FA, OTP 등) |
방식 | 사용 추천 시나리오 |
---|---|
JWT (JSON Web Token) | ✅ REST API, SPA, 모바일 앱 (서버 부담 적고, 확장성 좋음) |
Session (Django 기본 세션 인증) | ✅ 전통적인 웹 애플리케이션 (서버에서 보안 관리 가능) |
Django simple jwt
poetry add djangorestframework-simplejwt
'rest_framework_simplejwt',
-> INSTALLED_APPREST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
path('token', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh', TokenRefreshView.as_view(), name='token_refresh'), # 만료되기 전에 refresh 토근을 이용해 access토큰을 다시한번 받음
path('token/verify', TokenVerifyView.as_view(), name='token_verify'),
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
이부분을 변경해보자# utils/jwt_serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['user_name'] = user.username
# ...
return token
# settings.py
SIMPLE_JWT = {
# It will work instead of the default serializer(TokenObtainPairSerializer).
"TOKEN_OBTAIN_SERIALIZER": "utils.serializers.MyTokenObtainPairSerializer",
# ...
}
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from rest_framework import serializers
User = get_user_model()
class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password']
extra_kwargs = {
'password': {'write_only': True} # 응답값에 안나오게
}
# serializer의 validate 오버라이딩
# ✔ 기본적으로 ModelSerializer는 자동으로 필드 검증을 수행하지만,
# ✔ Django의 비밀번호 검증(validate_password())은 자동으로 실행되지 않음.
# ✔ 따라서 추가적으로 직접 validate_password()를 호출해야 비밀번호 강도 검증이 가능하다.
def validate(self, data):
user = User(**data) # 유저 모델의 인스턴스 제작
# validate_password()는 user에 User 객체를 기대함.
# ✔ **data는 단순한 딕셔너리(예: {'email': 'test@example.com', 'password': 'pass1234'})
# ✔ 하지만 validate_password()는 user 매개변수에 User 객체를 요구하므로, **data를 바로 넣으면 오류 발생
errors = dict()
try :
# Django 기본 패스워드 검증 함수 (settings.py에 설정된 기본 정책 사용)
validate_password(password=data['password'], user = user)
# 📌 validate_password()의 내부 동작:
# 1. 비밀번호 검증기(Validators)를 가져옴 (AUTH_PASSWORD_VALIDATORS 설정 기반)
# 2. 각 검증기(PasswordValidator)를 실행하여 비밀번호가 정책을 준수하는지 확인
# 3. 비밀번호가 정책에 맞지 않으면 ValidationError를 발생시킴
# 4. validate_password()에서 user를 전달하는 이유는, 비밀번호가 사용자의 속성과 유사하지 않은지 추가 검사를 수행하기 위해서임.
except ValidationError as e:
errors['password'] = list(e.messages) # 패스워드 검증 에러 메시지를 리스트로 저장
if errors :
raise serializers.ValidationError(errors)
return super().validate(data) # 검증이 끝나면 기본 검증 을 사용해 모든 검증 진행
#Django의 기본 검증 로직 예시:
# 필수 필드(required=True) 확인
# 모델 필드의 데이터 타입 확인 (IntegerField, EmailField 등)
# 커스텀 검증 로직 수행
class SignUpAPIView(CreateAPIView):
serializer_class = SignupSerializer
class SignUpSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password']
extra_kwargs = {
'password': {'write_only': True}
}
def create(self, validated_data):
user = User(**validated_data)
user.set_password(validated_data['password'])
user.save() # instance 가 있으면 create 없으면 update 호출
return user
refresh = RefreshToken.for_user(user)
복사후 붙여넣기, return 데이터 부분도 class SignUpAPIView(CreateAPIView):
serializer_class = SignUpSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
# refresh = RefreshToken.for_user(serializer.instance)
# response_data = {
# 'refresh': str(refresh),
# 'access': str(refresh.access_token),
}
# return Response(response_data, status=status.HTTP_201_CREATED, headers=headers)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
SimpleJWT
는 Django REST Framework(DRF)에서 JSON Web Token (JWT) 기반 인증을 간편하게 구현할 수 있도록 도와주는 패키지입니다. JWT는 클라이언트와 서버 간에 인증 정보를 안전하게 교환하기 위한 표준으로, 비공개 키로 서명된 토큰을 사용하여 사용자 인증을 처리합니다.
SimpleJWT
는 pip를 통해 간단하게 설치할 수 있습니다.
pip install djangorestframework-simplejwt
설치 후, Django의 settings.py
파일에 SimpleJWT
를 통합합니다.
REST_FRAMEWORK
에 SimpleJWT 설정 추가
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
SimpleJWT의 기본 설정
SimpleJWT
는 다양한 설정 옵션을 제공합니다. 기본적인 설정은 다음과 같습니다:
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
}
URL 설정
SimpleJWT
는 기본적인 JWT 발급과 갱신을 위한 뷰를 제공합니다. 이를 사용하기 위해 urls.py
에 다음과 같은 라우팅을 추가합니다:
from django.urls import path
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
TokenObtainPairView
POST /api/token/
: 사용자에게 액세스 토큰과 리프레시 토큰을 발급합니다.username
과 password
를 포함해야 합니다.// 요청 본문 예시
{
"username": "myuser",
"password": "mypassword"
}
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
TokenRefreshView
POST /api/token/refresh/
: 리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급합니다.refresh
토큰을 포함해야 합니다.// 요청 본문 예시
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
JWT를 사용하여 인증된 요청을 보내려면, Authorization
헤더에 Bearer
와 함께 액세스 토큰을 포함해야 합니다. 아래는 fetch 함수를 이용한 프론트엔드의 요청 코드 예시입니다.
// 액세스 토큰을 변수로 저장
const accessToken = 'your_access_token_here';
// API 요청 보내기
fetch('https://your-api-domain.com/api/protected-resource/', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data); // 요청에 대한 응답 처리
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
기본 제공되는 뷰 외에 사용자 정의 로직을 추가하고 싶을 때는, TokenObtainPairView
를 상속받아 커스텀 뷰를 작성할 수 있습니다.
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
토큰에 추가적인 사용자 정보를 포함시키려면, 기본 시리얼라이저를 상속받아 커스텀 시리얼라이저를 작성할 수 있습니다.
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework import serializers
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# 사용자 정의 클레임 추가
token['email'] = user.email
token['is_admin'] = user.is_staff
return token
커스텀 시리얼라이저를 사용하여 토큰 발급 시, 사용자에게 추가적인 정보를 포함한 응답을 전송할 수 있습니다.
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
# 추가적인 응답 데이터
data['username'] = self.user.username
data['email'] = self.user.email
return data
토큰 블랙리스트를 사용하면, 리프레시 토큰이 블랙리스트에 등록되어 더 이상 사용할 수 없게 할 수 있습니다. 이 기능을 활성화하려면, 다음과 같이 설정합니다.
앱 추가
INSTALLED_APPS = [
...
'rest_framework_simplejwt.token_blacklist',
]
블랙리스트 설정 활성화
SIMPLE_JWT = {
'BLACKLIST_AFTER_ROTATION': True,
}
로그아웃 처리
로그아웃 시 리프레시 토큰을 블랙리스트에 등록하여, 더 이상 사용할 수 없게 만듭니다.
from rest_framework_simplejwt.tokens import RefreshToken
def logout_view(request):
refresh_token = request.data["refresh"]
token = RefreshToken(refresh_token)
token.blacklist()
return Response(status=status.HTTP_205_RESET_CONTENT)
JWT의 클레임에 추가 정보를 포함시키려면, get_token
메서드를 재정의하여 추가 클레임을 설정할 수 있습니다.
from rest_framework_simplejwt.tokens import RefreshToken
def my_custom_token_view(request):
user = request.user
refresh = RefreshToken.for_user(user)
# 사용자 정의 클레임 추가
refresh['user_id'] = user.id
refresh['is_admin'] = user.is_staff
return Response({
'refresh': str(refresh),
'access': str(refresh.access_token),
})
토큰의 유효성을 직접 검사하고 싶을 때는, AccessToken
또는 RefreshToken
클래스를 사용할 수 있습니다.
from rest_framework_simplejwt.tokens import AccessToken
def validate_token_view(request):
token = request.data.get("token")
try:
token = AccessToken(token)
# 토큰이 유효한 경우
return Response({"valid": True})
except:
# 토큰이 유효하지 않은 경우
return Response({"valid": False}, status=status.HTTP_400_BAD_REQUEST)