[DRF] - Simple-JWT 를 이용한 Login, Logout, Refresh

KimJiHong·2023년 12월 14일
1

REST API

목록 보기
2/6
post-thumbnail

JWT 관련 설명글 - JWT Authentication 완전 정복기!

DRF & JWT Authentication

DRF에서는 기본적으로 JWT 인증을 지원하지 않아 다른 서드 파티 라이브러리를 사용해야한다.

공식문서를 보면 DRF에서 권장하는 JWT 라이브러리는 Simple-JWT이다.

이 글에서는 Simple-JWT에 구현된 Serializer와 View 클래스를 사용하여 HttpOnly 속성의 JWT를 발급받고, 인증을 구현할 것이다.

Simple-JWT's views.py

  • TokenViewBase
    • GenericAPIView를 상속받아 구현된 뷰 클래스.
    • 다른 토큰 관련 뷰 클래스들의 기본 베이스 클래스로 사용된다.
  • TokenObtainPairView
    • 사용자의 자격 증명을 기반으로 Access 및 Refresh Token을 생성.
    • 일반적으로 로그인 시에 사용되며, 사용자가 제공한 자격 증명이 올바른 경우 JWT 발급.
  • TokenRefreshView
    • Refresh Token을 사용하여 새로운 Access Token을 생성하는 역할.
    • 만료되지 않은 Refresh Token이 제공된 경우, 새로운 Access Token을 발급.
  • TokenObtainSlidingView
    • Sliding Token을 사용하여 Access Token과 새로운 Refresh Token을 생성.
    • 일반적으로 일정 시간동안 활동이 없는 사용자에게 유용하다.
  • TokenRefreshSlidingView
    • Sliding Token을 사용하여 Refresh Token을 갱신하고 새로운 Access Token을 생성.
  • TokenVerifyView
    • 제공된 토큰이 유효한지를 검증.
  • TokenBlacklistView
    • 특정 토큰을 블랙리스트에 추가하여 해당 토큰을 무효화.
    • 즉, 해당 토큰은 더 이상 사용할 수 없다.

Simple-JWT's serializers.py

  • TokenObtainSerializer
    • 로그인 시에 사용자의 자격 증명을 받아 Access Token을 발급.
  • TokenObtainPairSerializer
    • 로그인 시에 사용자의 자격 증명을 받아 Access 와 Refresh Token을 발급.
  • TokenObtainSlidingSerializer
    • Sliding Token을 사용하여 Access Token과 새로운 Refresh Token을 생성.
  • TokenRefreshSerializer
    • Refresh Token을 사용하여 새로운 Access Token을 생성.
  • TokenRefreshSlidingSerializer
    • Sliding Token을 사용하여 Refresh Token을 갱신하고 새로운 Access Token을 생성.
  • TokenVerifySerializer
    • 토큰이 유효한지를 검증하는 데 사용.
  • TokenBlacklistSerializer
    • 특정 토큰을 블랙리스트에 추가하는 데 사용.

Simple-JWT settings

Simple-JWT 설치

pip install djangorestframework-simplejwt

Simple-JWT 등록

# config/settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework_simplejwt',
]

REST_USE_JWT = True

REST_FRAMEWORK = {
    # ...
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # ...
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ]
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=3),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
    # ...
}

access 토큰의 유효기간 15분, refresh 토큰의 유효기간 3일로 설정했다.


JWT Login

로그인 시 refresh와 access 두개의 토큰을 발급 받기위해 TokenObtainPairView 를 그냥 사용하면 HttpOnly 속성이나 다른 옵션을 추가할 수 없고, JSON 형식으로 토큰 값을 전달해주기 때문에 TokenObtainPairView 클래스의 post 메소드를 오버라이딩해서 HttpOnly 속성의 토큰과 반환 데이터를 "login success" 로 변경했다.

# accounts/views.py
from rest_framework import status, generics
rom rest_framework_simplejwt.views import TokenObtainPairView

class LoginAPIView(TokenObtainPairView):
    # TokenObtainPairView 의 response 는 refresh, access 토큰 정보를 반환하기 때문에
    # "login success" 로 바꾸고 토큰은 쿠키에 담아서 응답.
    def post(self, request: Request, *args, **kwargs) -> Response:
        res = super().post(request, *args, **kwargs)
        
        response = Response({"detail": "login success"}, status= status.HTTP_200_OK)
        response.set_cookie("refresh", res.data.get('refresh', None), httponly= True)
        response.set_cookie("access", res.data.get('access', None), httponly= True)

        return response
        

# accounts/urls.py
urlpatterns = [
	# ...
	path('login', views.LoginAPIView.as_view()),
]

또, HttpOnly 속성의 토큰을 발급받을 때 주의할 점은 클라이언트의 스크립트에서 접근이 불가능해 Access Token 을 Authorization 헤더로 설정하지 못한다.

이를 해결하기 위해서는 View를 지나기전 MiddleWare 에서 Access Token을 Authorization 헤더로 설정해줘야 한다.

Authorization 헤더를 위한 MiddleWare 구현법

Refresh

Refresh API를 구현할 때는 Refresh 토큰 정보가 쿠키에 들어있기 때문에 reqeust 쿠키내 저장된 refresh 토큰을 추출해서 TokenRefreshSerializer 로 보내고, 반환될 refresh 및 access 토큰도 HttpOnly 속성을 줘야한다.

# accounts/views.py
from rest_framework import status, generics
rom rest_framework_simplejwt.views import TokenRefreshView

class CustomTokenRefreshView(TokenRefreshView):
    def post(self, request: Request, *args, **kwargs) -> Response:
        refresh_token = request.COOKIES.get('refresh', '토큰이 업서용')
        data = {"refresh": refresh_token}
        serializer = self.get_serializer(data= data)

        try:
            serializer.is_valid(raise_exception= True)
        except TokenError as e:
            raise InvalidToken(e.args[0])

        token = serializer.validated_data
        response = Response({"detail": "refresh success"}, status= status.HTTP_200_OK)
        response.set_cookie("refresh", token['refresh'], httponly= True)
        response.set_cookie("access", token['access'], httponly= True)

        return response
        
        
# accounts/urls.py
urlpatterns = [
	# ...
	path('refresh', views.CustomTokenRefreshView.as_view()),
]

Logout

JWT 인증 방식은 Session 과 달리 상태를 추적하지 않기 때문에 Refresh 토큰을 blacklist 에 등록하고, access 및 refresh 토큰을 쿠키에서 삭제함으로써 로그아웃을 구현해야 한다.

또, 인증되지 않은 사용자가 Logout 요청을 할경우를 막기 위해서 permission과 authentication 클래스를 지정해줬다.

# accounts/views.py
from rest_framework import status, generics
rom rest_framework_simplejwt.views import TokenRefreshView

class LogoutAPIView(TokenBlacklistView):
    permission_classes = [IsAuthenticated]
    authentication_classes = [JWTAuthentication]

    def post(self, request: Request, *args, **kwargs) -> Response:
        refresh_token = request.COOKIES.get('refresh', '토큰이 업서용')
        data = {"refresh": str(refresh_token)}
        serializer = self.get_serializer(data= data)

        try:
            serializer.is_valid(raise_exception= True)
        except TokenError as e:
            raise InvalidToken(e.args[0])

        response = Response({"detail": "token blacklisted"}, status= status.HTTP_200_OK)
        response.delete_cookie("refresh")
        response.delete_cookie("access")

        return response
        
# accounts/urls.py
urlpatterns = [
	# ...
	path('logout', views.LogoutAPIView.as_view()),
]
profile
https://h0ng.dev

0개의 댓글

관련 채용 정보