[ISSUE] signup AbstractBaseUser class
- -
[22.11.24] Signup-AbstractBaseUser
✔️ Django에서 제공하고 있는 제공하는 auth-user를 사용하면 회원가입 시 기본적으로 secret key를 이용하여 password를 암호화, 로그인 시 session을 이용한 인증 인가를 손쉽게 이용할 수 있다.
✔️ 하지만 현재 진행하고 있는 프로젝트는 auth-user를 사용하지 않고 다른 user모델을 생성하여 사용자의 정보를 관리할 수 있게 따로 분리하였다.
✔️ Simple-JWT를 이용할 예정이라 session을 이용한 대한 인증 인가를 사용하지 않고, User에 필요한 사용자 정보를 custom 할 필요가 있기 때문에 제공해 주는 auth-user를 사용하지 않았다.
🍃 User
📌 User Model
class User(models.Model):
""" 사용자 계정에 대한 정보 모델 """
name = models.CharField(verbose_name='이름', max_length = 20)
password = models.CharField(verbose_name='비밀번호', max_length = 15)
introduce = models.TextField(verbose_name='자기소개', max_length = 200)
profile_picture = models.ImageField(verbose_name='프로필사진', null=True, upload_to=f"profile/", blank=True)
blog_url = models.CharField(verbose_name='블로그url', max_length = 250)
# Payment_status choices
PAYMENT_ON = 1
PAYMENT_OFF = 0
PAYMENT_CHOICES = [
{PAYMENT_ON, '결제'},
{PAYMENT_OFF, '미결제'}
]
payment_status = models.IntegerField(choices=PAYMENT_CHOICES, default=PAYMENT_OFF, verbose_name='결제상태')
created_at = models.DateTimeField(verbose_name='생성일', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='갱신일', auto_now=True)
def __str__(self):
return f"{self.name}"
class Meta:
verbose_name = '유저'
verbose_name_plural = '유저 목록'
✔️ Django에서 제공하는 auth-user를 사용하지 않기 때문에 models.Model을 상속받아 작성하였다.
📌 User 생성(Signup View)
class SignupAPIView(APIView):
def post(self, request):
try:
email: str = request.GET['email']
password: str = request.GET['password']
userModel = User.objects.create(password=password)
emailModel = Email.objects.create(user=userModel, email=email)
return Response({'msg':'Success signup'})
except KeyError as k:
return Response({'msg':f'ERROR: Signup process KeyError that Class SignupAPIView : {k.args}'}, status=status.HTTP_400_BAD_REQUEST)
✔️ 클라이언트에서 입력한 password를 이용하여 User를 생성 후 User 모델을 foreign key로 가지는 Email을 생성하는 로직을 구현하였다.
** email, password로 User를 생성하지 않고 하위 Email모델이 email정보를 가지고 있는 이유는 User정보에 1:n으로 다중 email정보가 등록될 수 있기 때문에 모델을 분리하였다.
✔️ 이렇게 models.Model을 상속받은 모델을 이용하여 위와 같이 User를 생성하게 되면 password 정보가 DB에 저장될 때, 암호화를 거치지 않고 클라이언트에서 입력한 값 그대로 저장이 되기 때문에 보안상 위험성을 가지고 있다.
✔️ 그래서 Django 개발자는 대부분 제공해 주는 auth-user를 사용해서 내부 메서드를 통해 암호화를 거쳐 저장이 되는 기능을 사용한다.
하지만 일반적인 경우가 아닌 User모델을 확장하여 사용하는 방법은 아래와 같이 존재한다.
- 프록시 모델 사용하기
- User 모델과 일대일 관계의 프로필 테이블 추가하기
- AbstractUser 모델 상속한 사용자 정의 User 모델 사용하기
- AbstractBaseUser 모델 상속한 사용자 정의 User 모델 사용하기
✔️ 본인은 4번째 확장 방법인 AbstractBaseUser 모델을 상속한 사용자 정의 User로 변경해 보겠다.
🍃 AbstractBaseUser 클래스 상속의 장단점
📌 장점
✔️ AbstractBaseUser 모델을 상속한 User 커스텀 모델을 만들면 로그인 아이디로 이메일 주소를 사용하거나 Django 로그인 절차가 아닌 다른 인증 절차를 직접 구현할 수 있다.
✔️ 지금 구현하려는 회원가입/로그인도 마찬가지이며, 기존에 운영 중이던 PHP 설루션의 회원 DB를 그대로 재사용하고자 한다면 AbstractBaseUser 모델을 상속한 User 커스텀 모델을 만들어야 한다.
📌 단점
✔️ 운영 중에 시스템 사용자 모델을 변경하는 것이 매우 어렵다는 점이 있다. 이미 운영 중인 Django 기반 웹 사이트의 경우에는 그냥 기존 모델을 사용하는 것이 좋다.
🍃 AbstractBaseUser 상속받은 User
📌 변경 후 Model
class UserManager(BaseUserManager):
# 일반 user 생성
def create_user(self, password=None):
user = self.model()
user.set_password(password)
user.save(using=self._db)
return user
class User(AbstractBaseUser):
""" 사용자 계정에 대한 정보 모델 """
name = models.CharField(verbose_name='이름', max_length = 20)
password = models.CharField(verbose_name='비밀번호', max_length = 15, unique=True)
introduce = models.TextField(verbose_name='자기소개', max_length = 200)
profile_picture = models.ImageField(verbose_name='프로필사진', null=True, upload_to=f"profile/", blank=True)
blog_url = models.CharField(verbose_name='블로그url', max_length = 250)
# Payment_status choices
PAYMENT_ON = 1
PAYMENT_OFF = 0
PAYMENT_CHOICES = [
{PAYMENT_ON, '결제'},
{PAYMENT_OFF, '미결제'}
]
payment_status = models.IntegerField(choices=PAYMENT_CHOICES, default=PAYMENT_OFF, verbose_name='결제상태')
created_at = models.DateTimeField(verbose_name='생성일', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='갱신일', auto_now=True)
# User 모델의 필수 field
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
# 사용자의 username field는 password 설정
USERNAME_FIELD = 'password'
# 필수로 작성해야하는 field
REQUIRED_FIELDS = []
objects = UserManager()
def __str__(self):
return f"{self.name}"
class Meta:
verbose_name = '유저'
verbose_name_plural = '유저 목록'
✔️ User생성 시 create 대신 create_user를 사용하여 passwor의 암호화 적용을 위해 User 모델을 AbstractBaseUser를 상속
✔️ AbstractBaseUser 상속 시 필요 추가 사항
- is_active = models.BooleanField(default=True)
: 계정의 활성화/비활성화 구분 - is_admin = models.BooleanField(default=False)
: 관리자 계정으로 사용되는 계정인지 구분 - USERNAME_FIELD = 'password'
: 기준이 되는 칼럼 - REQUIRED_FIELD = []
: 필수 입력값으로 사용할 칼럼 설정 - objects = UserManage()
: create_user를 사용할 UserManager 클래스 할당
📌 UserManager
✔️ UserManager를 구현하여 User를 생성할 때 사용한 create_user를 분석하여 보자.
class UserManager(BaseUserManager):
# 일반 user 생성
def create_user(self, password=None):
user = self.model()
user.set_password(password)
user.save(using=self._db)
return user
📌 set_password()
def set_password(self, raw_password):
self.password = make_password(raw_password)
self._password = raw_password
✔️ set_password() 메서드 안에서 기존 password(raw_password)를 _password에 할당
✔️ raw_password를 make_password() 인자 값으로 전달
📌 make_password()
def make_password(password, salt=None, hasher='default'):
"""
Turn a plain-text password into a hash for database storage
Same as encode() but generate a new random salt. If password is None then
return a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string,
which disallows logins. Additional random string reduces chances of gaining
access to staff or superuser accounts. See ticket #20079 for more info.
"""
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
if not isinstance(password, (bytes, str)):
raise TypeError(
'Password must be a string or bytes, got %s.'
% type(password).__qualname__
)
hasher = get_hasher(hasher)
salt = salt or hasher.salt()
return hasher.encode(password, salt)
✔️ make_password() 메서드 내부까지 들어오면 매개변수로 받은 password를 hasher를 사용해서 encoding 해주고 있다.
📌 변경된 User 생성(Signup View)
class SignupAPIView(APIView):
def post(self, request):
try:
email: str = request.GET['email']
password: str = request.GET['password']
userModel = User.objects.create_user(password=password)
emailModel = Email.objects.create(user=userModel, email=email)
return Response({'msg':'Success signup'})
except KeyError as k:
return Response({'msg':f'ERROR: Signup process KeyError that Class SignupAPIView : {k.args}'}, status=status.HTTP_400_BAD_REQUEST)
✔️ View단에서 User생성으로 사용되던 create() 메서드를 create_user()로 변경하서 다시 회원가입을 진행하였을 때 password가 hasher로 암호화되어 DB에 저장되는 것을 확인하였다.
'ISSUE > Django' 카테고리의 다른 글
[ISSUE] Tag 포함된 Site 모델의 serialize 처리 (0) | 2023.05.06 |
---|---|
[ISSUE] ManytoMany 관계를 가진 두 모델 Bulk Create 작업 (1) | 2023.05.06 |
[ISSUE]decorator를 이용한 중복작업 전처리 (0) | 2023.05.05 |
소중한 공감 감사합니다