장고의 AbstractUser를 상속받아 커스텀 유저모델을 만들면 실제로 사용되지 않는
필요없는 필드가 생기는 문제가 있어 AbstractBaseUser를 상속받아서 커스텀 유저 모델을 만들었다.
import bcrypt
from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, username, password, name, email, address):
# 외부 라이브러리를 사용하여 비밀번호를 해시화
hashed_password = bcrypt.hashpw(
password.encode('utf-8'), bcrypt.gensalt())
user = self.model(
username=username,
# 바이트 문자열로 데이터베이스에 저장시 문제가 생길수 있어서 유니코드 문자열로 변환
password=hashed_password.decode('utf-8'),
name=name,
# 이메일의 도메인을 대문자로 받아도 소문자로 데이터베이스에 저장
email=self.normalize_email(email),
address=address
)
user.save()
return user
def create_superuser(self, username, email, name, password, address=''):
user = self.create_user(
username=username,
password=password,
name=name,
email=email,
address=address
)
user.is_superuser = True
user.save()
return user
class User(AbstractBaseUser):
username = models.CharField(max_length=50, unique=True)
name = models.CharField(max_length=20)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
email = models.EmailField(max_length=254, unique=True)
address = models.CharField(max_length=254)
# 고유 식별자로 사용되는 user모델의 필드 이름
USERNAME_FIELD = 'username'
# 필수로 입력받고 싶은 값으로 createsuperuser시 사용
REQUIRED_FIELDS = ['name', 'email']
objects = CustomUserManager()
def soft_delete(self):
self.is_active = False
self.save()
위와 같이 필요한 메소드를 오버라이딩하며 커스텀 매니저도 만들고
회원가입 로직도 완성하여 이제 로그인을 구현하려고 시도하는 중이였다.
로그인 로직은 지금까지 Django 프로젝트를 진행하면서 몇 번이나 만들었기 때문에 자신이 있었다.
class LoginView(APIView):
def post(self, request):
user = authenticate(username=request.data.get(
"username"), password=request.data.get("password"))
if not user:
return Response({"error": "아이디나 비밀번호가 틀립니다."}, status=status.HTTP_400_BAD_REQUEST)
serializer = UserSerializer(user)
res_data = serializer.data
refresh = RefreshToken.for_user(user)
res_data['refresh_token'] = str(refresh)
res_data['access_token'] = str(refresh.access_token)
return Response(res_data)
위의 코드의 문제점은 authenticate() 함수가 작동하지 않아 user를 가져올 수 없다는 것이였다.
이에 대한 문제를 찾아보니 authenticate() 함수가 Django의 기본 User 모델에서 제공하는 비밀번호 해싱 방식을 사용하여 인증을 시도하는데, 내가 bcrypt 라이브러리를 직접 사용하여 비밀번호를 해시화했기 때문에 충돌이 발생한다는 것을 알았다. Django는 비밀번호를 bcrypt로 해시화한 방식과 동일하게 처리하지 않기 때문에 인증이 실패하게 되는 것이다.
위의 문제를 해결하기 위해선 Django가 제공하는 set_password 메소드를 사용하거나 직접 인증 로직을 구현해야 하는 것 중 선택해야 했다.
근데 set_password 메소드를 사용할 거라면 커스텀 유저 모델을 만들 때 이미 썼을 것이다.
그래서 bcrypt 라이브러리를 사용하여 직접 인증 로직을 구현해보기로 했다.
import bcrypt
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from .models import User
from .serializers import UserSerializer
class LoginView(APIView):
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
try:
user = User.objects.get(username=username)
except:
return Response({"error": "아이디가 틀렸습니다."}, status=status.HTTP_400_BAD_REQUEST)
if bcrypt.checkpw(password.encode('utf-8'), user.password.encode('utf-8')):
serializer = UserSerializer(user)
res_data = serializer.data
refresh = RefreshToken.for_user(user)
res_data['refresh_token'] = str(refresh)
res_data['access_token'] = str(refresh.access_token)
return Response(res_data)
else:
return Response({"error": "비밀번호가 틀렸습니다."}, status=status.HTTP_400_BAD_REQUEST)
bcrypt.checkpw(password.encode('utf-8'), user.password.encode('utf-8')) 이 부분이 사용자가 입력한 비밀번호와 데이터베이스에 저장된 해시화된 비밀번호를 비교하여 일치하는지 확인하는 부분이다.
bcrypt.checkpw()는 두 개의 바이트 문자열을 인자로 받으며 첫 번째 인자는 사용자가 입력한 비밀번호고, 두 번째 인자는 데이터베이스의 해시화된 비밀번호이다. 입력받은 평문 비밀번호를 해시화한 후, 이 해시값이 저장된 해시화된 비밀번호와 일치하는지 확인하는 함수이다.
인자값을 바이트 문자열로 받기 때문에 .encode('utf-8')를 이용해서 두 문자열을 바이트 문자열로 바꿔줘야 한다.
user모델을 불러오는 과정에서 에러가 많이 나서 예외처리로 구현했기에 이게 좋은 방법인지는 모르겠다.
이 부분에 대한 피드백을 받고 나서 프로젝트를 다시 진행해야 겠다.
'웹 개발' 카테고리의 다른 글
[프로젝트] API로 호출한 데이터를 받는 방법 (0) | 2024.09.30 |
---|---|
[프로젝트] 유저 정보 PUT 메소드 요청시 검증 로직 (2) | 2024.09.27 |
[프로젝트] ERD 수정 및 와이어프레임 작성 (0) | 2024.09.24 |
[프로젝트] 내일 배움 캠프 최종 프로젝트 시작 (2) | 2024.09.24 |
[웹 개발] POSTMAN으로 로그인 권한 쉽게 주기 (1) | 2024.09.14 |