[Django] Custom User모델과 이메일 로그인 (Custom User model and login by email)
#1. AbstratcBaseUser
장고에는 기본적으로 제공하는 유저 관리 모델에는 User, AbstractUser, AbstractBaseUser 3가지가 있습니다.
가장 기본적인 AbstractBaseUser에서 차례로 상속하여 내려가는 구조입니다.
User 모델이나 AbstractUser 모델의 경우 대부분 필요한 기능들을 제공하지만 커스터마이징이 힘든 단점이 있습니다.
반면에 베이스 모델인 AbstractBaseUser 모델의 경우 password와 last_login 단 2개의 최소 필드를 제공하면서
시스템에 맞는 커스터마이징이 가능합니다.
로그인 수단으로 이메일을 사용하는 경우 혹은 소셜 로그인같은 경우 allauth 같은 서드파티모듈을 도입할 수 있지만
이 역시 커스터마이징이 쉽지는 않은 관계로 AbstractBaseUser 모델을 상속한 CustomUser모델을 만들어 이메일
로그인을 구현하였습니다.
#2. Manager
장고의 Object Manager는 모델의 헬퍼 클래스로 장고 ORM의 핵심적인 기능, 즉 DB의 쿼리 실행을 담당합니다.
Model.objects.all(), count() 등의 당연히 쓰고 있는 메서드가 바로 Manager에 속한 메서드입니다.
따라서 유저가 새로 생성한 모델을 포함한 장고의 모든 모델은 Manager와 연결되어있습니다.
장고의 유저관련 Manager로 BaseUserManger 클래스가 존재합니다.
User 모델에서 사용가능했던 create_user와 같은 전용 메서드를 관리하는 Manager인데,
이메일 로그인을 위해 이 Manager를 상속한 CustomUserManger를 생성합니다.
from django.contrib.auth.models import BaseUserManager
class CustomUserManager(BaseUserManager):
def _create_user(self, email, nickname, password, **extra_fields):
if not email:
raise ValueError("이메일 주소를 설정해주세요.")
if not nickname:
raise ValueError("닉네임을 설정해주세요.")
email = self.normalize_email(email) # 중복 최소화를 위한 소문자로 정규화
user = self.model(email=email, nickname=nickname, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, nickname, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(email, nickname, password, **extra_fields)
def create_superuser(self, email, nickname, password, **extra_fields):
user = self.create_user(email=email, password=password, nickname=nickname)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
#3. CustomUser 모델 생성
CustomUser 모델의 경우 어플리케이션에 적용할 유저의 속성 필드와 boolean 필드를 정의해주면 됩니다.
중요한건 위에 설정한 Manager를 연결하는 것입니다.
CustomUser는 위에서 서술한대로 AbstractBaseUser를 상속받으며 추가적으로 PermissionsMixin이라는 장고의
허가권 그룹 프레임워크도 상속 받습니다.
추가적으로 구현한 has_perm, has_module_perms는 상속받은 PermissionsMixin의 메서드를 변형한 것입니다.
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
nickname = models.CharField(max_length=30)
date_joined = models.DateTimeField(verbose_name="date joined", auto_now_add=True)
last_login = models.DateTimeField(verbose_name="last login", auto_now=True)
bio = models.CharField(max_length=150, blank=True)
img = models.ImageField(default="profile_img/testuser1testusercom-2022-03-28.png", upload_to="profile_img")
friends = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
like_keyword = models.CharField(max_length=100, default="", blank=True)
# Boolean field
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_recommend_on = models.BooleanField(default=True)
is_like_public = models.BooleanField(default=True)
# https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#django.contrib.auth.models.CustomUser
USERNAME_FIELD = "email" # 이메일 로그인
REQUIRED_FIELDS = ["nickname"] # REQUIRED_FIELDS: 필수적으로 받고 싶은 값
# 위에서 정의한 Manager 지정
objects = CustomUserManager()
def __str__(self):
return self.email
# is_admin이 True면 obj에 대한 권한을 가짐
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
#4. Authentication Backends
이메일을 로그인 수단으로 쓰기 위해 사용자 인증을 위한 백엔드를 설정 및 지정해주어야 합니다.
APP에 backends.py 파일을 따로 만들어 기존 장고에서 제공하는 auth 모듈의 ModelBackend를 상속한 EmailBackends 클래스를 생성하였습니다.
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
user_model = get_user_model()
if username is None:
username = kwargs.get(user_model.USERNAME_FIELD)
try:
user = user_model.objects.get(email=username)
if user.check_password(password): # check valid password
return user # return user to be authenticated
except user_model.DoesNotExist: # no matching user exists
return None
(token 인증으로 리팩토링을 하였을때는 따로 backend 생성없이 단지 authenticate 관련 함수를 별도로 만들었습니다.)
마지막으로 프로젝트 settings에 생성한 유저 모델과 이메일 백엔드를 명시해줍니다.
(현재 프로젝트에서 User 관련 기능을 담당하는 app은 accounts 입니다.)
# settings.py
AUTH_USER_MODEL = "accounts.CustomUser"
AUTHENTICATION_BACKENDS = ["accounts.backends.EmailBackend"]
참고로 AUTH_USER_MODEL의 default는 auth.User 입니다.
즉, 장고의 기본 User모델외의 사용자 정의 유저모델은 settings에 필수로 명시해야하며 authentication 역시 AUTH_USER_MODEL을 참조합니다.
그리고 이러한 모든 일련의 활동은 프로젝트 초기 및 마이그레이션 전에 설정하는 것이 좋습니다.
그렇지 않으면 운영하는 중간에 DB 스키마를 꼬아버릴 가능성이 농후합니다.
(장고 공식문서에도 나와있는 권고사항입니다.)
#5. 참고