- [ISSUE] signup AbstractBaseUser class2023년 05월 05일
- 조별하
- 작성자
- 2023.05.05.:31
[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 다음글이전글이전 글이 없습니다.댓글