React.js + Django 로그인 구현 1

DARTZ·2023년 4월 24일
2

Projects

목록 보기
1/5

서론

Django와 React.js에서 로그인 기능과 회원가입 기능을 구현하는 방법입니다.
원문
위 글을 번역하고 필요한 내용은 추가하면서 만들었습니다.

본문

완벽한 인증 작업을 구현할 예정입니다. Project 설정, Git 설정, Backend구성, JWT (access + refresh)구성, Api들 생성, Frontend의 private end point 설정, route-guard, 브라우저에 사용자 정보 저장을 React hooks만으로(Redux 없이) 구현하려고 합니다. Backend와 Frontend 2부분으로 나누어져 있고 Backend부터 시작합니다.

목차

  • 필요항목들
  • 프로젝트 설정 (Backend)
  • 프로젝트 시작
  • private endpoint 만들기

필요항목들

  • 먼저 파이썬을 설치해주세요.

python -V를 터미널에서 입력해보면 python이 설치 되었는지 아닌지 알 수 있습니다.

  • 아무 IDE 프로그램, VS Code나 pycharm을 추천드립니다.
  • Git 버전 관리 (선택 사항이지만 추천드립니다.)

프로젝트 설정 (Backend)

  1. 프로젝트를 시작할 Main 폴더를 만들어주세요.
    Main폴더 안에는 backend와 frontend 폴더를 만들면 됩니다. Main 폴더를 만들고 폴더 안에서 터미널을 통해 git init 명령어를 입력해주세요.

  2. backend 폴더로 이동해주세요.
    cd backend

  3. 파이썬 가상환경을 만들고 실행해주세요.
    vnev폴더 안의 Scripts 폴더 안의 activate.bat 파일을 실행하면 가상 환경이 활성화됩니다.
    python -m venv venv
    source venv/bin/activate # Mac or Linux
    venv\Scripts\activate.bat # Window는 폴더 경로가 /가 아닌 \입니다..!

  4. Django를 설치하고 프로젝트를 만들어봅시다.
    가상환경을 실행시킨 상태에서 python -m pip install Django를 입력해주시면 Django가 설치됩니다. 그리고 django-admin startproject backend .를 입력해주면 backend라는 이름의 Django Project가 생성됩니다.

  5. 추가적인 패키지와 requirements.txt 파일을 생성해봅시다.
    Django Rest Framework와 JWT 토큰을 설치합니다.
    pip install djangorestframework django-cors-headers djangorestframework-simplejwt PyJWT
    그런 다음에 requirements.txt를 만들어 주면 됩니다.
    pip freeze > requirements.txt

  6. Django app을 생성합니다.
    python manage.py startapp api

requirements.txt 파일이 다음과 같이 되어 있을텐데 버전이 다른건 상관 없습니다.
asgiref==3.4.1 autopep8==1.6.0 Django==4.0.1 django-cors-headers==3.11.0 djangorestframework==3.13.1 djangorestframework-simplejwt==5.0.0 pycodestyle==2.8.0 PyJWT==2.3.0 pytz==2021.3 sqlparse==0.4.2 toml==0.10.2 tzdata==2021.5

프로젝트 시작

api폴더 안에 serializer.py 파일을 만들고 다음과 같이 작성합니다.

# api/serializer.py

from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        # Frontend에서 더 필요한 정보가 있다면 여기에 추가적으로 작성하면 됩니다. token["is_superuser"] = user.is_superuser 이런식으로요.
        token['username'] = user.username
        token['email'] = user.email
        return token

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(
        write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)

    class Meta:
        model = User
        fields = ('username', 'password', 'password2')

    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError(
                {"password": "Password fields didn't match."})

        return attrs

    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data['username']
        )

        user.set_password(validated_data['password'])
        user.save()

Serializers는 Django REST Framework에서 자바스크립트와 Frontend에서 이해할 수 있는 data type(json)으로 변환해주는 역할입니다.

  • 코드를 살펴보면
    MyTokenObtainPairSerializer는 토큰을 생성합니다. (구체적으로는 access와 refresh 토큰들). 만약 정상적인 username과 password가 들어온다면 access 토큰을 해석해서 username과 email을 가져옵니다. RegisterSerializer는 기본적으로 데이터베이스에 사용자 정보를 저장합니다. (회원가입)

  • 이제 views와 URLs를 만들 차례입니다.

# api/urls.py

from django.urls import path
from . import views

from rest_framework_simplejwt.views import (
    TokenRefreshView,
)

urlpatterns = [
    path('token/', views.MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('register/', views.RegisterView.as_view(), name='auth_register'),
    path('', views.getRoutes)
]
# api/views.py

from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.http import JsonResponse
from api.serializer import MyTokenObtainPairSerializer, RegisterSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework import generics
from django.contrib.auth.models import User
from rest_framework.permissions import AllowAny, IsAuthenticated

# Create your views here.

class MyTokenObtainPairView(TokenObtainPairView):
    serializer_class = MyTokenObtainPairSerializer

class RegisterView(generics.CreateAPIView):
    queryset = User.objects.all()
    permission_classes = (AllowAny,)
    serializer_class = RegisterSerializer


@api_view(['GET'])
def getRoutes(request):
    routes = [
        '/api/token/',
        '/api/register/',
        '/api/token/refresh/'
    ]
    return Response(routes)
# project_name/urls.py (위 과정을 그대로 따라오셨다면 backend/urls.py입니다.)

from django.contrib import admin
from django.urls import path
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include("api.urls"))
]


지금까지 폴더구조는 위와 같습니다.

  • backend/settings.py파일에 내용을 업데이트 해줍니다.

경고! 그대로 복사 붙여넣기 하면 안됩니다.

# Add this import
from datetime import timedelta

# Update INSTALLED_APPS
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # 추가
    'rest_framework_simplejwt.token_blacklist', # 추가
    'corsheaders', # 추가
    'api', # 추가
]

# Update TEMPLATES
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# Update MIDDLEWARE
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'corsheaders.middleware.CorsMiddleware', # 추가
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# 추가
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}

# JWT 토큰 설정입니다.
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # ACCESS Token의 유효기간
    'REFRESH_TOKEN_LIFETIME': timedelta(days=50), # Refresh 토큰의 유효기간 
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',

    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': 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),
}

CORS_ALLOW_ALL_ORIGINS = True
  • Migrate 해주고 실행시켜보면 됩니다.
    python manage.py makemigrations
    python manage.py migrate
    python manage.py runserver

  • superuser를 생성하러면 다른 터미널에서 python manage.py createsuperuser를 입력해주면 됩니다. (아이디나 비밀번호를 추가적으로 입력하게 됩니다.)

  • http://localhost:8000/api/ 로 이동하면 생성한 api들을 확인할 수 있습니다.

  • 사용가능한 endpoint들이 위에 나타나 있습니다. 바로 테스트 해볼 수 있습니다.

  • http://localhost:8000/api/token/ 로 이동해서 바로 superuser의 아이디와 비밀번호를 입력하면 access토큰과 refresh 토큰을 얻을 수 있습니다.

모든 로그인마다 새로운 access token이 사용자 id, email, 이름 기반으로 생성될 것입니다. 새로운 access token이 refresh token 기반으로 생성됩니다. 만약에 같은 refresh 토큰이 반복되어서 사용되면 차단 될 것입니다.

** 테스트를 위해서 private endpoint를 만들어봅시다.

# api/views.py

from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.decorators import api_view, permission_classes

@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def testEndPoint(request):
    if request.method == 'GET':
        data = f"Congratulation {request.user}, your API just responded to GET request"
        return Response({'response': data}, status=status.HTTP_200_OK)
    elif request.method == 'POST':
        text = request.POST.get('text')
        data = f'Congratulation your API just responded to POST request with text: {text}'
        return Response({'response': data}, status=status.HTTP_200_OK)
    return Response({}, status.HTTP_400_BAD_REQUEST)
profile
사람들이 비용을 지불하고 사용할 만큼 가치를 주는 서비스를 만들고 싶습니다.

0개의 댓글