본문 바로가기

Backend/Python

[drf] JWT인증 추가

반응형

JWT인증 하는 방식을 추가 구현하여 보안을 높이려고 하였다.

Java와는 다르게 기본적인 보안을 적용하기 용이했다.

쿼리를 별도로 작성하지 않고 메서드를 통해 중복확인과 비밀번호 해시화를 기본 메서드를 이용해 작성할수있었다.

하지만 인증을 진행하기 위해서는 그런 부분을 만족시킨 상태에서 인증이 진행되어야 했기에 처음 구현시 이 부분을 알지 못해 원하는 동작을 이끌어 내지 못했다.

csrf도 drf에서 기본적으로 제공되어 jwt를 쿠키에 담아 인증하도록 구현했다.

password 확인시 동일하게 나오는데 check_password는 통과하지 못했다.

 

찾아보니 auth를 사용하면 hash화를 시켜줘야 하게 되는데 기본적으로 그냥 string으로 저장하게 되어 이런 일이 생겼다고 한다.

따라서 회원가입 로직부터 변경해주었다.

 

# JWTAuthentication.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.conf import settings
from ..serializers import MembersList
from ..models import MemberInfo
import jwt


class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        print("JWTAuthentication authenticate called")  # 디버깅 로그 추가
        token = request.COOKIES.get('jwt')
        if not token:
            return None

        try:
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
        except jwt.ExpiredSignatureError:
            raise AuthenticationFailed('Token has expired')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('Invalid token')

        try:
            user = MemberInfo.objects.get(id=payload['user_id'])
        except MemberInfo.DoesNotExist:
            raise AuthenticationFailed('User not found')
        return (user, None)


class EmailBackend(ModelBackend):
    def authenticate(self, request, email=None, password=None, **kwargs):
        print("EmailBackend authenticate called")  # 디버깅 로그 추가
        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(email=email)
            print(user)
        except UserModel.DoesNotExist:
            return None
        print("before chk passwd")
        print(user.password)
        print(password)
        if user.check_password(password):
            print(password)
            print(user)
            return user
        return None

    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

 

# serializers.py
from django.contrib.auth.models import Group, User
from django.conf import settings
from rest_framework import serializers
from .models import MemberInfo


class MemberSerializer(serializers.ModelSerializer):
    class Meta:
        # # model 지정
        # model = MemberInfo
        model = settings.AUTH_USER_MODEL
        fields = ['id', 'nickName', 'email', 'password']


class VerifyMember(serializers.ModelSerializer):
    class Meta:
        model = settings.AUTH_USER_MODEL
        fields = ['id', 'email', 'password']


class MembersList(serializers.ModelSerializer):
    class Meta:
        model = settings.AUTH_USER_MODEL
        fields = ['id', 'nickName', 'email', 'memberAuth', 'memberState']


class LoginSerializer(serializers.Serializer):
    email = serializers.EmailField()
    password = serializers.CharField()

 

# models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models


# Create your models here.
class MemberInfoManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('이메일을 입력해야 합니다.')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)  # 비밀번호를 해시화합니다.
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        return self.create_user(email, password, **extra_fields)


class MemberInfo(AbstractBaseUser, PermissionsMixin):
    class MemberAuth(models.TextChoices):
        USER = 'USER', 'User'
        ADMIN = 'ADMIN', 'Admin'

    class MemberState(models.TextChoices):
        ACTIVE = 'A', 'Active'
        INACTIVE = 'I', 'Inactive'
        BANNED = 'B', 'Banned'

    nickName = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    password = models.CharField(max_length=100) # 실제로 의미가 있지 않다.
    memberAuth = models.CharField(max_length=100, choices=MemberAuth.choices, default=MemberAuth.USER)
    memberState = models.CharField(max_length=1, choices=MemberState.choices, default=MemberState.ACTIVE)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    objects = MemberInfoManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.email

    def to_dic(self):
        return {
            "userId": self.id,
            "nickName": self.nickName,
            "email": self.email,
            "password": self.password,
            "memberAuth": self.memberAuth,
            "memberState": self.memberState
        }

 

# views.py
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.hashers import make_password
from django.http import JsonResponse

from rest_framework import status
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .addon.JWTAuthentication import JWTAuthentication
from .models import MemberInfo
from .serializers import MemberSerializer, VerifyMember, MembersList, LoginSerializer
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
import jwt
import json


# Create your views here.

@api_view(['GET'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsAuthenticated])
def index(request):
    return render(request, 'index.html')


@swagger_auto_schema(
    method='get',
    operation_description="Render the signup page",
    responses={
        200: openapi.Response('Signup page rendered'),
        404: 'Not Found'
    }
)
@csrf_exempt
@api_view(['GET'])
def member_signup(request):
    return render(request, 'signup.html')


@swagger_auto_schema(
    method='post',
    request_body=MemberSerializer,
    responses={
        201: openapi.Response('Created', MemberSerializer),
        400: 'Bad Request'
    }
)
@api_view(['POST'])
def member_signup_ajax(request):
    # if request.method == 'POST':
    #     serializer = MemberSerializer(data=request.data)
    #     print(request)
    #     print(serializer)
    #     if serializer.is_valid():
    #         serializer.save()
    #         return Response(serializer.data)
    #     return Response(serializer.errors)
    if request.method == 'POST':
        try:
            data = json.loads(request.body)
            User = get_user_model()

            # 이메일 중복 확인
            if User.objects.filter(email=data['email']).exists():
                return JsonResponse({'error': '이미 존재하는 이메일입니다.'}, status=400)

            # 사용자 생성
            user = User.objects.create(
                nickName=data['nickName'],
                email=data['email'],
                password=make_password(data['password']),  # 비밀번호 해시화
                # memberAuth=data['memberAuth'],  # 필요한 경우 추가 필드
                # memberState=data['memberState'],  # 필요한 경우 추가 필드
            )
            return JsonResponse({'message': '회원가입 성공'}, status=201)
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=500)
    return JsonResponse({'error': '잘못된 요청 방식입니다.'}, status=400)

@swagger_auto_schema(
    method='get',
    operation_description="Render the login page",
    responses={
        200: openapi.Response('Login page rendered'),
        404: 'Not Found'
    }
)
@csrf_exempt
@api_view(['GET'])
def member_login(request):
    return render(request, 'login.html')


@swagger_auto_schema(
    method='post',
    request_body=MemberSerializer,
    responses={
        200: openapi.Response('Ok', MemberSerializer),
        400: 'Bad Request',
        401: 'Unauthorized'
    }
)
@api_view(['POST'])
def member_login_ajax(request):
    if request.method == 'POST':
        serializer = LoginSerializer(data=request.data)

        if serializer.is_valid():
            email = serializer.validated_data.get('email')
            password = serializer.validated_data.get('password')
            try:
                # member = MemberInfo.objects.get(email=email, password=password)
                # # 사용자 인증에 성공하면 사용자 데이터를 반환합니다
                # return Response(MemberSerializer(member).data, status=status.HTTP_200_OK)
                user = authenticate(request, email=email, password=password)
                if user is not None:
                    token = jwt.encode({'user_id': user.id}, settings.SECRET_KEY, algorithm='HS256')
                    response = Response()
                    response.set_cookie(
                        key='jwt',
                        value=token,
                        httponly=True,
                        secure=True,
                        samesite='Strict',
                        max_age=3600,
                    )
                    response.data = {'message': 'Login successful'}
                    return response
                else:
                    return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
            except MemberInfo.DoesNotExist:
                # 사용자 인증에 실패하면 401 Unauthorized 응답을 반환합니다
                return Response({"error": "Invalid email or password"}, status=status.HTTP_401_UNAUTHORIZED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@swagger_auto_schema(
    method='get',
    operation_description="Render the memberList page",
    responses={
        200: openapi.Response('Members page rendered'),
        404: 'Not Found'
    }
)
@csrf_exempt
@api_view(['GET'])
def members_list(request):
    if request.method == 'GET':
        members = MemberInfo.objects.all()
        serializer = MembersList(members, many=True)
    return render(request, 'admin.html', {'members': serializer.data})


@csrf_exempt
@api_view(['POST'])
def members_update(request):
    if request.method == 'POST':
        for member_data in request.data:
            try:
                member = MemberInfo.objects.get(id=member_data['id'])
                member.memberAuth = member_data['memberAuth']
                member.memberState = member_data['memberState']
                member.save()
            except MemberInfo.DoesNotExist:
                return Response({"error": "Member not found"}, status=status.HTTP_404_NOT_FOUND)
        return Response({"success": "Members updated successfully"}, status=status.HTTP_200_OK)
    return Response({"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST)

 

 

정상 작동을 확인

jwt도 쿠키에 정상적으로 발급되었다.

반응형

'Backend > Python' 카테고리의 다른 글

[DRF] JWTAuthentication Errors  (0) 2024.07.24
[DRF] Django Rest Framework (1-2)  (0) 2024.07.12
[DRF] Django Rest Framework (1-1)  (0) 2024.07.11
[DRF] Django Rest Framework (1)  (0) 2024.07.09
[DRF] Django Rest Framework (0)  (0) 2024.07.09