Django JWT

Haks.·2025년 2월 10일
0

Study

목록 보기
54/65

JWT

  • Json Web Token
  • jwt.io(Decode 사이트)
  • Login, 유저인증 ...
  • Session 기반이 아닌 Token 기반 인증 방식

📌 ✅ 특징:

  • 서버는 사용자의 세션을 저장하지 않음 → Stateless (무상태)
  • 토큰 자체에 사용자 정보가 포함되므로 빠르게 인증 가능
  • 탈취되면 무제한 사용될 가능성이 있음 (추가 보안 필요)

📌 Session

유저 정보를 매번 가져오는것이 Session 형태, key:value 형태

  • 유저를 가져오려 할때마다 매번 서버로 요청해서 DB에서 받아와야 한다.
{
    "key" : 1
}

📌 Token

  • 헤더(알고리즘과 타입), PAYLOAD(데이터), SIGNATURE(변조됬는지 확인)
  • payload 에 데이터가 있어 클라이언트 는 payload부분만 게속 가지고 사용하게 된다. 그래서 그걸 게속 쓰는거다
  • 시그니쳐 부분이 신뢰가 가면 사용하는것
  • DB 에 굳이 확인하지 않고 사용한다.

JWT (JSON Web Token) vs Session (Django Session)

비교 항목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 JWT

Django simple jwt

  1. jdango simple jwt 검색 -> installation
  2. poetry add djangorestframework-simplejwt
  3. Project Configuration : 설정
  4. 'rest_framework_simplejwt', -> INSTALLED_APP
  5. settings.py
REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        ...
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
    ...
}
  1. url.py
    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'),
  1. POSTMAN을 통해 토큰 실험, POST -> raw로 useranme, password 전송
  2. access 부분 긁어서 jwt.io 에 넣어보면 data 나옴

토큰으로 받을 데이터 수정

  1. "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer", 이부분을 변경해보자
  2. 옆쪽의 Customizing token claims에 설명 나와있음
  3. 아래의 내용에서 데이터를 얻고자 하는부분을 Add custom claims 에 추가해서 넣은후
# 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
  1. my_app 부분 적용할 내 앱 이름으로 변경후 settings.py 에 추가
# settings.py
SIMPLE_JWT = {
  # It will work instead of the default serializer(TokenObtainPairSerializer).
  "TOKEN_OBTAIN_SERIALIZER": "utils.serializers.MyTokenObtainPairSerializer",
  # ...
}
  1. 다시 POSTMAN 으로 POST 요청을 보낸 후 jwt.io 에 들어가 확인해 보면 추가적으로 등록된 것을 볼 수 있다.

🧑‍💻 실습 JWT를 이용한 회원가입 APP

  1. user app 등록후 serializer.py 제작
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 등)
    	# 커스텀 검증 로직 수행
  1. views.py 제작, url 제작
class SignUpAPIView(CreateAPIView):
    serializer_class = SignupSerializer
  1. POSTMAN 으로 signup 해보자, 적은 패스워드 해서 보내보자 오류 확인
  2. 응답으로 비번이 안오게 만들어보자, write_only 추가
class SignUpSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'password']
        extra_kwargs = {
            'password': {'write_only': True}
        }
  1. shell 로 확인해보면 근데 정적인 password로 들어가있다 암호화 해주자
  2. serializers 에서 처리 해보기
def create(self, validated_data):
    user = User(**validated_data)
    user.set_password(validated_data['password'])
    user.save() # instance 가 있으면 create 없으면 update 호출


    return user
  1. Token 으로 response 해줄수 도 있음
  2. CreateAPIView -> post, create -> create 부분 긁어와서 views에 넣어서 오버라이딩
  3. simple jwt 사이트에서 Creating tokens manually 접속 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)
  1. 그리고 다시 postman 으로 보내면 토큰화 되서 나온다
  2. 근데 이토큰은 우리가 만든 세팅 부분을 거치지 않고 적용되는거라 이토큰은 username이 안나온다.
  3. 회원가입하고 유저쪽에서 토큰을 한번더 받는것을 추천

SimpleJWT

SimpleJWT는 Django REST Framework(DRF)에서 JSON Web Token (JWT) 기반 인증을 간편하게 구현할 수 있도록 도와주는 패키지입니다. JWT는 클라이언트와 서버 간에 인증 정보를 안전하게 교환하기 위한 표준으로, 비공개 키로 서명된 토큰을 사용하여 사용자 인증을 처리합니다.


1. SimpleJWT의 주요 특징

  • 토큰 기반 인증: 세션이 아닌 토큰을 사용하여 인증을 처리하므로, 클라이언트와 서버 간의 상태를 유지할 필요가 없습니다.
  • 액세스 토큰 및 리프레시 토큰: 두 종류의 토큰을 사용하여 보안을 강화하고, 리프레시 토큰을 통해 액세스 토큰을 갱신할 수 있습니다.
  • 확장성과 사용자 정의: 기본 제공되는 동작을 쉽게 확장하거나 커스터마이징할 수 있습니다.

2. SimpleJWT 설치 및 설정

설치

SimpleJWT는 pip를 통해 간단하게 설치할 수 있습니다.

pip install djangorestframework-simplejwt

Django 설정에 SimpleJWT 추가

설치 후, Django의 settings.py 파일에 SimpleJWT를 통합합니다.

  1. REST_FRAMEWORK에 SimpleJWT 설정 추가

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        ),
    }
  2. 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',
    }

3. SimpleJWT 기본 사용법

JWT 인증 엔드포인트 설정

  1. 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'),
    ]
  2. TokenObtainPairView

    • POST /api/token/: 사용자에게 액세스 토큰과 리프레시 토큰을 발급합니다.
    • 요청 본문에 usernamepassword를 포함해야 합니다.
    // 요청 본문 예시
    {
      "username": "myuser",
      "password": "mypassword"
    }
    • 응답 예시:
    {
      "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
      "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
    }
  3. 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);
});

4. 커스텀 JWT 뷰 및 시리얼라이저

커스텀 토큰 발급 뷰

기본 제공되는 뷰 외에 사용자 정의 로직을 추가하고 싶을 때는, 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

5. 고급 설정 및 확장

토큰 블랙리스트

토큰 블랙리스트를 사용하면, 리프레시 토큰이 블랙리스트에 등록되어 더 이상 사용할 수 없게 할 수 있습니다. 이 기능을 활성화하려면, 다음과 같이 설정합니다.

  1. 앱 추가

    INSTALLED_APPS = [
        ...
        'rest_framework_simplejwt.token_blacklist',
    ]
  2. 블랙리스트 설정 활성화

    SIMPLE_JWT = {
        'BLACKLIST_AFTER_ROTATION': True,
    }
  3. 로그아웃 처리

    로그아웃 시 리프레시 토큰을 블랙리스트에 등록하여, 더 이상 사용할 수 없게 만듭니다.

    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)

6. 참고 자료

0개의 댓글

관련 채용 정보