JWT with DRF

이현준·2022년 6월 9일
0

Django

목록 보기
4/4

JWT?

Session based authentication

  1. 클라이언트에서 사용자의 인증 정보를 서버로 전달
  2. 서버는 인증 처리 후, 해당 user에 대한 session 생성
  3. session 정보는 서버에 저장되고, 클라이언트는 session id를 받아서 브라우저에 저장
  4. 클라이언트는 이후 요청에 session id를 이용
  5. 서버는 클라이언트에서 보낸 session id를 이용해서 저장 중인 session 정보를 찾아 인증 처리
  6. session id가 만료되면 위 과정 반복

session을 이용한 인증에서는 중요한 session 정보를 서버에서 관리한다.
그래서 django에서 session 인증을 사용하면 django_session 테이블이 생성되고 session 정보가 저장된다.

위와 같은 Session based authentication의 단점은 클라이언트에서 session id를 보내올 때마다 그에 맞는 session 정보를 찾기위해 DB를 탐색해야 한다는 것이다. 또한, DB를 app마다 분리 해 놓은 경우에는 더 찾기 힘들고 관리가 힘들다.

이런 단점을 극복하기 위한 방법이 JWT!

JWT

  1. 클라이언트에서 사용자의 인증 정보를 서버로 전달
  2. 서버는 인증을 처리하고 JWT를 생성해 클라이언트로 전달
  3. 클라이언트는 JWT를 브라우저에 저장하고, 이후 요청에 JWT 사용
  4. 서버는 클라이언트가 보낸 JWT를 검증하여 인증 처리
  • Session 인증과의 주요 차이점
    • session 정보는 서버에 저장되지만, JWT는 서버에 저장 되지 않는다.
    • 클라이언트가 보낸 session id를 이용해 session 정보를 찾지만, JWT 인증에서는 토큰을 바로 검증한다.

JWT 방식에서는 정보를 토큰 안에 저장하기 때문에, django에서 사용할 때 session과 달리 테이블이 생성되지 않는다.

JWT 구성

Header-Payload-Signature
  • Header
    • JWT의 메타 정보
    • 토큰 타입, 어떤 알고리즘이 쓰였는지
      {
      "typ":"JWT",
      "alg":"HS256"
      }
  • Payload
    • 토근 유효 기간
    • 유저 정보와 같은 데이터 저장 영역
    • 인증 정보가 아닌 특정 유저를 찾을 수 있는 값을 담음
      {
      "token_type": "access",
      "exp": 1649145719,
      "jti": "1foo2jwt3id4",
      "user_id": 123
      }
  • Signature
    • 인코딩 된 Header, Payload, secret를 합쳐 Header에서 지정한 알고리즘으로 hashing해주는 역활(보안을 위한 부분)
    • Header, Payload, secret 중 하나라도 변조되면 클라이언트에서의 signature와 서버에서 hashing한 signature가 다르게 됨

session 인증과 다르게 DB 탐색을 하지 않고, JWT의 Signature를 이용해 검증하기 때문에 더 빠르게 인증 처리가 가능하다.

JWT with DRF

Install

simplejwt 라이브러리 사용

pip install djangorestframework-jwt

# 소셜 로그인 지원 라이브러리
# pip install django-allauth

# DRF에서 회원가입, 로그인 기능 지원
# 참고 https://velog.io/@jcinsh/DRF-11-Django-REST-Auth
# pip install django-rest-auth

settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated', # 인증된 사용자만 접근 가능
        # 'rest_framework.permissions.IsAdminUser', # 관리자만 접근 가능
        # 'rest_framework.permissions.AllowAny', # 누구나 접근 가능
    ),
	
    'DEFAULT_AUTHENTICATION_CLASSES': (
	       # JWT를 기본 인증 방식으로 지정
       'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        # 'rest_framework.authentication.TokenAuthentication',
        # 'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.BasicAuthentication',
    ),
}

# 추가적인 JWT_AUTH 설젇
JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,
    'JWT_ALGORITHM': 'HS256', # 암호화 알고리즘
    'JWT_ALLOW_REFRESH': True, # refresh 사용 여부
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 유효기간 설정
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=28), # JWT 토큰 갱신 유효기간
}

app.serializers.py

JWT token 발행

...
from rest_framework_jwt.settings import api_settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import update_last_login
...


# JWT 사용을 위한 설정
JWT_PAYLOAD_HANDLER = api_settings.JWT_PAYLOAD_HANDLER
JWT_ENCODE_HANDLER = api_settings.JWT_ENCODE_HANDLER

class LoginSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=30)
    password = serializers.CharField(max_length=128, write_only=True)
    token = serializers.CharField(max_length=255, read_only=True)	
    
    def validate(self, data):
        username = data.get("username")
        password = data.get("password", None)
        # 사용자 아이디와 비밀번호로 로그인
        user = authenticate(username=username, password=password)

        if user is None:
            return {'username': 'None'}
        try:
        	# Payload 생성
            payload = JWT_PAYLOAD_HANDLER(user)
            # jwt_token 생성
            jwt_token = JWT_ENCODE_HANDLER(payload)
            update_last_login(None, user)

        except User.DoesNotExist:
            raise serializers.ValidationError(
                'username and password does not exist'
            )
        return {
            'username': user.username,
            'token': jwt_token
        }
        

app.views.py

...
# JWT 사용을 위해 필요
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
# generics cbv
from rest_framework import generics
...

class Login(generics.GenericAPIView):
    serializer_class = UserLoginSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        
        if not serializer.is_valid(raise_exception=True):
            return Response({"message": "Request Body Error."}, status=status.HTTP_409_CONFLICT)

        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data
        if user['username'] == "None":
            return Response({"message": "fail"}, status=status.HTTP_401_UNAUTHORIZED)
        
        return Response(
            {
                "user": UserSerializer(
                    user, context=self.get_serializer_context()
                ).data, 
                "token": user['token']
            }
        )

app.urls.py

from django.urls import path
from django.conf.urls import url

from . import views

urlpatterns = [
    path('login', views.Login.as_view()),
] 

참고
JWT란? : https://www.qu3vipon.com/django-jwt
djangorestframework-jwt :https://moondol-ai.tistory.com/174?category=865999 / https://velog.io/@kong2520/DRF-JWT-로그인-authentication

profile
기록, 정리하는 습관 만들기

0개의 댓글