[졸프] Django 회원가입, 로그인, 로그아웃 API with JWT

맹수연·2022년 5월 17일
2

졸업 프로젝트

목록 보기
3/3

스타트 당시 기획했던 서비스는 '사용자의 대답에 따라 다음 동화의 장면을 AI가 자동으로 생성'하는 웹 서비스였다. GPT3를 사용하여 다양한 방법으로 input을 넣고 결과를 받아봐도 올바른 문장 형태조차 만들지 못하여 주제를 바꾸게 되었다.

그렇게 바꾸게 된 주제는 'AI 기반 참여형 동화 웹 서비스'

주제 선정이나 디테일한 기획 부분은 각설하고 내가 맡게 된 백엔드 파트에서 했던 것 중 User 정보 관련 API(회원가입, 로그인, 로그아웃)와 JWT 발급에 대해 설명하고자 한다.

  1. 회원가입
  2. 로그인(JWT 발급)
  3. 로그아웃

1. 회원가입

프론트 부분은 따로 만들었기 때문에 API에 대해서만 언급한다.
앱 이름은 accounts로 했다.

Backend/settings.py
(프로젝트 이름을 Backend로 만들었다)
세팅 파일에서 INSTALLED_APPS 부분에 꼭

'accounts', 

이걸 추가해야 한다.

accounts/views.py

def signup(request): 
    if request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = UserSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors)

accounts/urls.py

from django.urls import path
from accounts import views
from django.conf.urls import include

app_name = 'accounts'

urlpatterns = [
    path('signup', views.signup),
]

accounts/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser) :
    username = models.CharField(max_length=255, unique=True)
    email = models.CharField(max_length=200, unique = True)
    password = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)

    REQUIRED_FIELDS = []

Django는 기본적으로 제공하는 user 모델이 있기 때문에 이를 사용하여 필요한 필드만 수정하고 추가하는 방식으로 사용했다. username = 보통 생각하는 유저 id 로 사용했는데 요즘에는 id 안쓰고 메일만 쓰는 경우도 많아서 그런 부분은
'Django user model custom' 이라는 키워드로 구글링하면 정보를 찾을 수 있을 것이다.

이 이후에

python manage.py makemigration <app 이름>, 
python manage.py migration <app 이름>

까지 해줘야 db에 테이블이 생성된다.

accounts/serializers.py

from rest_framework import serializers
from .models import User

class UserSerializer(serializers.ModelSerializer) :
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'password', 'created_at']
        extra_kwargs = {
            'password' : {'write_only' : True}
        }

    def create(self, validated_data):
        password = validated_data.pop('password', None)
        instance = self.Meta.model(**validated_data)
        if password is not None :
            instance.set_password(password)
        instance.save()
        return instance

유저의 비밀번호는 reponse에 들어가면 안되기 때문에 extra_kwargs 부분을 작성하여 response에는 비밀번호를 제외하게 하였고, db에 유저 데이터가 들어갈 때도 비밀번호는 set_password로 암호화하여 저장하였다.

accounts/admin.py

from django.contrib import admin
from .models import User

admin.site.register(User)

2. 로그인 (JWT 발급)

accounts/views.py

@csrf_exempt
def login(request):
    if request.method == 'POST':
        data = JSONParser().parse(request)
        search_username = data['username']
        password = data['password']

        user = User.objects.filter(username=search_username).first()


        if user is None:
            raise AuthenticationFailed('User not found!') 
        
        if not user.check_password(password):
            raise AuthenticationFailed('Incorrect password!') 

        payload= {
            'username' : user.username,
            'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes=60),
            'iat' : datetime.datetime.utcnow()
        }

        token = jwt.encode(payload, JWT_SECRET, algorithm = 'HS256').decode('utf-8')

        response = JsonResponse({
            'message' : 'ok'
        })

        response.set_cookie(key ='jwt', value= token, httponly=False, samesite='None') 

        return response

JWT를 발행하여 로그인을 구현했다. token을 발행할 때 여러 값이 필요한데 그 값을 payload에 담는다.

<payload에 들어갈 인자 값 참고>

  • exp : expiration으로 토큰의 유효 시간을 의미
    - seconds, minutes, hours, days, weeks 사용 가능
  • iat : 토큰이 발행된 시간
  • iss : 토큰 발급자 (issuer)
  • sub : 토큰 제목 (subject)
  • aud : 토큰 대상자 (audience)
  • nbf : Not Before 를 의미, 토큰의 활성 날짜와 비슷한 개념
    - NumericDate 형식으로 지정한 날짜가 지나기 전까지 토큰 처리 x
  • jti : JWT의 고유 식별자, 주로 중복적인 처리 방지에 사용
    - 일회용 토큰에 사용하면 유용하다고 함

JWT_SECRET은 .env파일에 설정해두고 토큰 인코딩과 디코딩 할 때 사용할 수 있게 했다.
생성한 토큰을 쿠키에 넣기 위해 set_cookie 함수를 사용해 구현했다.

**참고로 samesite 속성은 프론트와 백엔드 도메인이 달라서 cors 문제를 해결하기 위해 추가하였다. 도메인이 같다면 굳이 추가 안해도 된다.

accounts/urls.py

from django.urls import path
from accounts import views
from django.conf.urls import include

app_name = 'accounts'

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

3. 로그아웃

accounts/views.py

@csrf_exempt
def logout(request) :
    if request.method == 'POST' :
        response = JsonResponse({
            "message" : "success"
        })
        response.delete_cookie('jwt')
        return response

delete_cookie 함수를 사용하여 쿠키에서 토큰을 제거하는 방식으로 로그아웃을 구현했다.

accounts/urls.py

from django.urls import path
from accounts import views
from django.conf.urls import include

app_name = 'accounts'

urlpatterns = [
    path('signup', views.signup),
    path('login', views.login),
    path('logout', views.logout),
]

서비스에 있어서 회원가입, 로그인 이런 기능들이 굉장히 기본적이기 때문에 쉽다고 생각했는데 생각보다 잔에러가 많이 나고 토큰 발행도 무한에러,,

블로그를 쓰면서 참고하면 좋은 것 같은 글을 발견해서 내가 보기 위해 기록함 -> 비밀번호 암호화 및 JWT 토큰 발행

0개의 댓글