DRF JWT 토큰 로그인 방식 - 토큰 커스텀하기

장현웅·2023년 10월 10일
0

🥚 TokenObtainPairView


TokenObtainPairView는 JWT 토큰 로그인 방식에서 사용자 인증을 위해 Json Web Token(JWT) 토큰을 얻는데 사용되는 클래스 기반 뷰(CBV)입니다.

DRF의 simplejwt 패키지를 설치하면 사용할 수 있습니다.

[ Terminal ]

pip install djangorestframework-simplejwt
[ settings.py ]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}
[ urls.py ]

from rest_framework_simplejwt.views import TokenObtainPairView

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
]

엔드포인트 'api/token'로 요청을 보내면 DRF의 위 패키지에서 제공하는 기본 클래스 뷰인 TokenObtainPairView가 사용자에게 JWT 토큰을 발급합니다. (= 로그인)

🐣 로그인


TokenObtainPairView의 역할

  • 인증 : 사용자가 ID와 비밀번호로 로그인 POST 요청을 하면, 이 뷰는 제공된 정보를 기반으로 사용자를 인증합니다.
  • 토큰 생성 : 사용자의 자격 증명(ID & Password)이 유효하면 이 뷰는 JWT 쌍(Access Token & Refresh Token)을 생성합니다.
    • Access Token : 사용자를 일정 시간 동안 인증하는 데 사용됩니다. 사용자는 사용자 인증이 필요한 HTTP 요청 헤더에 이 토큰을 실어 자신의 신원을 증명하는데 사용합니다.
    • Refresh Token : Access Token이 만료되었을 때, 새 Access Token을 얻는데 사용됩니다. 일반적으로 로그인 연장 등에 사용되며 Access Token보다 더 긴 유효 기간을 가집니다.
  • 응답 : 사용자의 자격 증명 후 토큰이 생성되면 이 뷰는 토큰을 응답으로 반환하며, 일반적으로 JSON 데이터로 반환됩니다. 사용자는 발급받은 토큰을 쿠키 또는 로컬 스토리지에 안전하게 저장합니다.

🐤 JWT 토큰 커스터마이징


클래스 기반 뷰(CBV)의 가장 큰 장점은 Reusable하다는 점입니다. 부모 클래스에 정의된 뷰 로직을 자식 클래스에서는 상속을 통해 필요한 메서드를 오버라이드하여 사용할 수 있습니다.

rest_framework_simplejwt.views의 TokenObtainPairView를 상속받아 새롭게 정의한 메서드를 통해 시리얼라이저를 커스터마이징 해보겠습니다.

  1. 먼저 원하는 엔드포인트를 설정합니다.
[ urls.py ]

from django.urls import path
from users import views

urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login_view'),
]
  1. TokenObtainView의 로직을 살펴보면, 사용되는 serializer가 정의되어 있습니다. 이것을 제가 정의한 시리얼라이저를 쓰기 위해 TokenObtainView를 상속받은 클래스뷰를 정의합니다.
[ TokenObtainView ]

class TokenObtainPairView(TokenViewBase):
    """
    Takes a set of user credentials and returns an access and refresh JSON web
    token pair to prove the authentication of those credentials.
    """

    _serializer_class = api_settings.TOKEN_OBTAIN_SERIALIZER


token_obtain_pair = TokenObtainPairView.as_view()
[ views.py ]

from rest_framework_simplejwt.views import TokenObtainPairView

class LoginView(TokenObtainPairView):
    serializer_class = LoginSerializer
  1. DRF의 JWT 로그인 방식에 사용되는 TokenObtainPairSerializer를 상속하여 Serializer를 커스터마이징하여 재정의합니다.
[ serializers.py ]

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class LoginSerializer(TokenObtainPairSerializer):

    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)

        token['email'] = user.email
        token['username'] = user.username
        token['profile_img'] = user.profile_img.url

        return token

토큰에 원하는 사용자 정보를 담을 수 있습니다.
  1. TokenObtainPairView에 정의된 _serializer_class = api_settings.TOKEN_OBTAIN_SERIALIZER 설정에 대해 settings.py에 재정의한 serializer를 TOKEN_OBTAIN_SERIALIZER로 설정합니다. 이 설정에서는 토큰의 유효기간 등 여러 설정이 가능합니다.
[ settings.py ]

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=720),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    "ROTATE_REFRESH_TOKENS": False,
    "BLACKLIST_AFTER_ROTATION": False,
    "UPDATE_LAST_LOGIN": False,

    "ALGORITHM": "HS256",
    "SIGNING_KEY": SECRET_KEY,
    "VERIFYING_KEY": "",
    "AUDIENCE": None,
    "ISSUER": None,
    "JSON_ENCODER": None,
    "JWK_URL": None,
    "LEEWAY": 0,

    "AUTH_HEADER_TYPES": ("Bearer",),
    "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
    "USER_ID_FIELD": "id",
    "USER_ID_CLAIM": "user_id",
    "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",

    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
    "TOKEN_TYPE_CLAIM": "token_type",
    "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",

    "JTI_CLAIM": "jti",

    "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
    "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
    "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),

    #### customize한 serializer로 변경함.
    
    "TOKEN_OBTAIN_SERIALIZER": "users.serializers.LoginSerializer",
    #### "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
    
    "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
    "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
    "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
    "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
    "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
  1. 이제 로그인 요청으로 발급받은 토큰을 jwt.io에서 디코딩해보면, 사용자의 정보가 포함된 것을 볼 수 있습니다.

🐥 Front) 로컬 스토리지에 토큰 저장하기


[ login.html ]

<body>
  <div>
      <label>이메일:</label>
      <input type="email" id="id_email"/>
  </div>
  <div>
      <label>비밀번호:</label>
      <input type="password" id="id_password"/>
  </div>
  <div>
      <button type="button" onclick="handleLogin()">로그인</button>
  </div>
</body>
이메일:
비밀번호:
로그인
[ login.js ]

async function handleLogin() {

    const email = document.getElementById('id_email').value
    const password = document.getElementById('id_password').value

    const response = await fetch('http://127.0.0.1:8000/users/login/', {

        headers : {
            'Content-type': 'application/json',
        },
        method : 'POST',
        body : JSON.stringify({
            "email" : email,
            "password" : password
        })
    })
    
        if (response.status == 200) {

        const response_json = await response.json()

        localStorage.setItem("access", response_json.access)
        localStorage.setItem("refresh", response_json.refresh)
        

        const base64Url = response_json.access.split('.')[1];
        const base64 = base64Url.replace(/-/g, '+').replace(/_/g,'/');
        const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));

        localStorage.setItem("payload",jsonPayload);

        alert('로그인 성공')

        window.location.href = "/wish/main.html"
    } 

🐓 로그아웃


JWT 토큰 로그인 방식에서의 로그아웃은 사용자의 정보가 저장된 토큰을 로컬스토리지에서 없애주면 됩니다.

<button type="button" onclick="handleLogout()">로그아웃</button>

로그아웃

function handleLogout() {
    localStorage.removeItem('access')
    localStorage.removeItem('refresh')
    localStorage.removeItem('payload')
}

🥚🐣🐤🐥🐓🐔

0개의 댓글