Bootstrap

深入分析 AbstractBaseUser 和 AbstractUser 的区别

AbstractBaseUser

AbstractBaseUser中只含有3个字段: password, last_login和is_active,方法也仅包含以下一些身份验证功能和密码存储功能。

def save(self, *args, **kwargs):
    super().save(*args, **kwargs)
    if self._password is not None:
        password_validation.password_changed(self._password, self)
        self._password = None

def get_username(self):
    """Return the username for this User."""
    return getattr(self, self.USERNAME_FIELD)

def clean(self):
    setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username()))

def natural_key(self):
    return (self.get_username(),)

# 是否是匿名用户
@property
def is_anonymous(self):
    """
    Always return False. This is a way of comparing User objects to
    anonymous users.
    """
    return False

# 身份认证
@property
def is_authenticated(self):
    """
    Always return True. This is a way to tell if the user has been
    authenticated in templates.
    """
    return True

# 通过createsuperuser创建出来的用户密码是用set_password进行哈希加密的
# 一般在用户注册时会使用,会将密码进行加密处理时会用到它。
def set_password(self, raw_password):
    self.password = make_password(raw_password)
    self._password = raw_password

# 检查密码
# 一般在用户登录时会使用,会将密码进行解密处理时会用到它。
def check_password(self, raw_password):
    """
    Return a boolean of whether the raw_password was correct. Handles
    hashing formats behind the scenes.
    """
    def setter(raw_password):
        self.set_password(raw_password)
        # Password hash upgrades shouldn't be considered password changes.
        self._password = None
        self.save(update_fields=["password"])
    return check_password(raw_password, self.password, setter)

def set_unusable_password(self):
    # Set a value that will never be a valid hash
    self.password = make_password(None)

def has_usable_password(self):
    """
    Return False if set_unusable_password() has been called for this user.
    """
    return is_password_usable(self.password)

def get_session_auth_hash(self):
    """
    Return an HMAC of the password field.
    """
    key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
    return salted_hmac(key_salt, self.password).hexdigest()

@classmethod
def get_email_field_name(cls):
    try:
        return cls.EMAIL_FIELD
    except AttributeError:
        return 'email'

@classmethod
def normalize_username(cls, username):
    return unicodedata.normalize('NFKC', username) if isinstance(username, str) else username

注意一:用户唯一身份的字段

但是需要注意的是你的拓展模型必须有一个可用于识别目的的唯一字段。这可以是一个用户名,一个电子邮件地址,或任何其他独特的属性。如果你使用可以支持的自定义认证后端,则允许使用非唯一的用户名字段。下列 identifier 将被用作识别字段。
识别字段:能代表用户唯一身份的字段,比如是 username、email 等

class MyUser(AbstractBaseUser):
    identifier = models.CharField(max_length=40, unique=True)
    ...
    # identifier 字段将被用作识别字段
    USERNAME_FIELD = 'identifier'

注意二:自定义管理器实现

由于 AbstractBaseUser 不像 AbstractUser 已经实现了 create_user 和 create_superuser 以及获取唯一 key 的方法(get_by_natural_key, 用于获取 USERNAME_FIELD 的用户),所以需要自定义管理器实现这些方法:

class UserManage(models.Manager):
    def create_user(self, username, password, **extra_fields):
        user = self.model(username=username, **extra_fields)
        if password:
            user.set_password(password)

        user.save()
	
	# 默认的认证后端(ModelBackend)需要使用这个属性,所以这里需要定义
	# 不然会报错:AttributeError: 'UserManage' object has no attribute 'get_by_natural_key'
	"""
	默认的认证后端比较简单,通过 get_by_natural_key(使用 USERNAME_FIELD 指定的字段的内容检索用户实例。) 获取到登录用户,
	再检查密码和活跃状态。
	
	"""
    def get_by_natural_key(self, username):
        return self.get(username=username)

注意三:自定义认证后端

不管是 AbstractBaseUser 或是 AbstractUser 它们都有一个默认的认证后端,名叫 ModelBackend,里面进行一些简单的用户密码以及用户活跃的功能校验,但是我们可能需要其他的校验,比如验证码校验、微信校验、或者复杂的用户名密码校验等。

默认的用户校验后端:

class ModelBackend(BaseBackend):
    """
    Authenticates against settings.AUTH_USER_MODEL.
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        if username is None or password is None:
            return
        try:
            user = UserModel._default_manager.get_by_natural_key(username)
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

如何查看怎么使用的 ModelBackend,django 默认在 global_settings.py 中指定了 :

AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']

获取校验的 ModelBackend:
如果 settings 中自己自定义了校验后端,直接获取,如果没有定义则会加载使用父级的( global_settings.py ) AUTHENTICATION_BACKENDS 中指定的默认的校验后端(ModelBackend),
因为 settings.py 会导入 global_settings.py 的所有内容。

def _get_backends(return_tuples=False):
    backends = []
    for backend_path in settings.AUTHENTICATION_BACKENDS:
        backend = load_backend(backend_path)
        backends.append((backend, backend_path) if return_tuples else backend)
    if not backends:
        raise ImproperlyConfigured(
            'No authentication backends have been defined. Does '
            'AUTHENTICATION_BACKENDS contain anything?'
        )
    return backends

自定义自己的认证后端:

from django.contrib.auth import get_user_model

User = get_user_model()


class BackendBase:
    def get_user(self, pk):
        try:
            return User.objects.get(pk=pk)
        except User.DoesNotExist:
            return None


class PasswordBackend(BackendBase):
    """
    密码登录
    """
    def authenticate(self, request, username, password, **kwargs):
        try:
            user = User.objects.get(username=username)

            if not user.check_password(password):
                raise Exception('密码错误!!')

            return user
        except User.DoesNotExist:
            raise Exception('账号不存在')

# settings.py 中配置如下内容:
AUTHENTICATION_BACKENDS = [
   'djcelerytest.backends.PasswordBackend',
]

继承 AbstractBaseUser 流程:

from django.contrib.auth.base_user import AbstractBaseUser


class UserManage(models.Manager):
    def create_user(self, username, password, **extra_fields):
        user = self.model(username=username, **extra_fields)
        if password:
            user.set_password(password)

        user.save()
	
	# USERNAME_FIELD 指定的字段
    def get_by_natural_key(self, username):
        return self.get(username=username)


class User(AbstractBaseUser):
    objects = UserManage()
    username = models.CharField('用户名', max_length=40, unique=True)
    phone = models.CharField(max_length=40, unique=True, null=True, default=None, verbose_name='手机号')
    # 该字段由 Django 的 AbstractBaseUser 中的方法使用,我们自己不建议使用,总是忽略,
    # 因为我们自己定义了认证后端,不在使用默认的 Modelbackend 认证后端了,如果么有自定义,那么改字段有效
    USERNAME_FIELD = 'username'
    created_at = models.DateTimeField(auto_now_add=True)
    class Meta:
        verbose_name = "用户"

    def __str__(self):
        return self.username


# settings.py 中配置:
AUTH_USER_MODEL = "app名称.User"

"""
默认是 global_settings.py 中的 AUTH_USER_MODEL
它的指向是 django auth 里面的 user:  AUTH_USER_MODEL = 'auth.User'

解释:
	有时我们可能不使用 auth 里面的内容,比如我们自己做一些本地测试的内容,
	但实际开发中我们可能借助于 auth 里面的一些内容,比如创建用户、密码加密、登录、认证等信息,
	使用 auth 里面的 AbstractBaseUser 和 AbstractUser 有利于我们开发。
	
	为什么拓展之后要更改指向:
		所以这里拓展之后必须将指向改为我们现有项目的用户模型,这样是为了应用我们用户模型的字段和	
		AbstractBaseUser 或 AbstractUser中的字段,告诉 django 使用我新定义的 User 表来做
		用户认证并且我们用户模型也永远了里面的方法。

	为什么 migrate:
		将 AbstractBaseUser 或 AbstractUser中的字段和我们项目中的用户字段更新到一张表里面去。
"""

注意四:什么时候使用 AbstractBaseUser

AbstractBaseUser 里面是没有创建用户和创建超级用户项的,比如你想创建一些复杂的用户,可能随机生成一些特有格式的用户名,那么 AbstractUser 中的 create_user 是做不到的,这个时候可以使用 AbstractBaseUser 并自己在管理器中实现 create_user 方法。或者想定义一些自己的认证方法,比如电子邮件登录、手机验证登录等。
如果使用 AbstractBaseUser 那么 auth 中的很多方法是用不了的,或者说是不适合的,比如:创建用户、认证、权限、管理器等
AbstractBaseUser 中没有创建用户方法。
AbstractBaseUser 只有一个默认的认证方式 ModelBackend。
AbstractBaseUser 没有权限相关的方法。
AbstractBaseUser 没有用户管理器提供的方法。
基于以上考虑:如果你对默认的auth_user中有不满意的地方或者不想要的字段, 或者只想保留默认的密码储存方式, 则可以选择这一方式。
反之就是如果你对django自带的User model刚到满意, 又希望额外的field的话, 你可以扩展AbstractUser类。

AbstractUser

实现了一个功能齐全的用户模型,并且可以使用 auth 中提供的所有方法,是比较成熟的一套用户体系。
他继承AbstractBaseUser和PermissionsMixin,所以也会有一些密码校验和权限管理的方法。
因此有需要的话可以从中继承并添加自己的配置字段和方法。

官网解释:
如果你对Django的User模型非常满意,但你想添加一些额外的配置文件信息,你可以将Django.contrib.auth.models.AbstractUser子类化,并添加你的自定义配置文件字段,尽管我们建议使用一个单独的模型,如指定自定义用户模型. AbstractUser提供了默认User作为抽象模型的完整实现。

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username and password are required. Other fields are optional.
    """
    objects = UserManager()

提供了创建用户方法:

class UserManager(BaseUserManager):
    
    def create_user(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, email, password, **extra_fields)

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

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

AbstractBaseUser 里面有的他也都有,AbstractUser 没有的他也有,方法都同上,不再多赘述。

;