Django 中自定义身份验证

Django 自带的身份验证功能足以满足大多数常见情况,但您可能有一些需求无法通过开箱即用的默认设置来满足。自定义项目中的身份验证需要了解所提供系统的哪些点是可扩展的或可替换的。本文档详细介绍了如何自定义身份验证系统。

身份验证后端 提供了一个可扩展的系统,用于在需要针对与 Django 默认不同的服务对存储在用户模型中的用户名和密码进行身份验证时使用。

您可以为您的模型赋予自定义权限,这些权限可以通过 Django 的授权系统进行检查。

您可以扩展默认的User模型,或者替换一个完全自定义的模型。

其他身份验证来源

有时您可能需要连接到另一个身份验证源——即用户名和密码或身份验证方法的另一个来源。

例如,您的公司可能已经设置了 LDAP,其中存储着每个员工的用户名和密码。如果用户在 LDAP 和基于 Django 的应用程序中拥有单独的帐户,那么对于网络管理员和用户来说都是一件很麻烦的事情。

因此,为了处理这种情况,Django 身份验证系统允许您插入其他身份验证源。您可以覆盖 Django 默认的基于数据库的方案,或者您可以将默认系统与其他系统一起使用。

请参阅身份验证后端参考,了解 Django 附带的身份验证后端信息。

指定身份验证后端

在幕后,Django 保持着一个“身份验证后端”列表,它会检查这些后端进行身份验证。当有人调用django.contrib.auth.authenticate()(如如何登录用户中所述)时,Django 会尝试在其所有身份验证后端进行身份验证。如果第一个身份验证方法失败,Django 会尝试第二个方法,依此类推,直到尝试所有后端。

要使用的身份验证后端列表在AUTHENTICATION_BACKENDS设置中指定。这应该是一个 Python 路径名称列表,这些名称指向知道如何进行身份验证的 Python 类。这些类可以位于 Python 路径上的任何位置。

默认情况下,AUTHENTICATION_BACKENDS设置为

["django.contrib.auth.backends.ModelBackend"]

这是检查 Django 用户数据库并查询内置权限的基本身份验证后端。它不提供任何速率限制机制来防止暴力破解攻击。您可以使用自定义身份验证后端实现自己的速率限制机制,也可以使用大多数 Web 服务器提供的机制。

AUTHENTICATION_BACKENDS的顺序很重要,因此,如果相同的用户名和密码在多个后端中有效,Django 将在第一个正向匹配处停止处理。

如果后端引发PermissionDenied异常,则身份验证将立即失败。Django 不会检查后续的后端。

注意

用户完成身份验证后,Django 会将用于对用户进行身份验证的后端存储在用户的会话中,并在需要访问当前已验证的用户时,在该会话期间重复使用相同的后端。这有效地意味着身份验证源是基于每个会话缓存的,因此,如果您更改AUTHENTICATION_BACKENDS,则如果需要强制用户使用不同的方法重新进行身份验证,则需要清除会话数据。一种简单的方法是执行Session.objects.all().delete()

编写身份验证后端

身份验证后端是一个实现两个必需方法的类:get_user(user_id)authenticate(request, **credentials),以及一组可选的与权限相关的授权方法

get_user方法接受一个user_id(可以是用户名、数据库 ID 或任何其他内容,但必须是用户对象的主键)并返回一个用户对象或None

authenticate方法接受一个request参数和凭据作为关键字参数。大多数情况下,它看起来像这样

from django.contrib.auth.backends import BaseBackend


class MyBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None):
        # Check the username/password and return a user.
        ...

但它也可以对令牌进行身份验证,如下所示

from django.contrib.auth.backends import BaseBackend


class MyBackend(BaseBackend):
    def authenticate(self, request, token=None):
        # Check the token and return a user.
        ...

无论哪种方式,authenticate()都应检查它获取的凭据,如果凭据有效,则返回与这些凭据匹配的用户对象。如果无效,则应返回None

requestHttpRequest,如果未将其提供给authenticate()(它会将其传递给后端),则可能是None

Django 管理员与 Django 用户对象紧密耦合。例如,要让用户访问管理员,User.is_staffUser.is_active必须为True(有关详细信息,请参见AdminSite.has_permission())。

解决此问题的最佳方法是为后端中存在的每个用户(例如,在您的 LDAP 目录、外部 SQL 数据库等中)创建一个 Django User对象。您可以编写脚本提前执行此操作,或者您的authenticate方法可以在用户第一次登录时执行此操作。

这是一个后端示例,它根据在您的settings.py文件中定义的用户名和密码变量进行身份验证,并在用户第一次进行身份验证时创建 Django User对象。在此示例中,创建的 Django User对象是一个超级用户,它将对管理员拥有完全访问权限

from django.conf import settings
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User


class SettingsBackend(BaseBackend):
    """
    Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

    Use the login name and a hash of the password. For example:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
    """

    def authenticate(self, request, username=None, password=None):
        login_valid = settings.ADMIN_LOGIN == username
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # Create a new user. There's no need to set a password
                # because only the password from settings.py is checked.
                user = User(username=username)  # is_active defaults to True.
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return None

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

处理自定义后端中的授权

自定义身份验证后端可以提供其自身的权限。

用户模型及其管理器会将权限查找函数(get_user_permissions()get_group_permissions()get_all_permissions()has_perm()has_module_perms()with_perm())委派给实现这些函数的任何身份验证后端。

授予用户的权限将是所有后端返回的所有权限的超集。也就是说,Django 向任何一个后端授予权限的用户授予权限。

如果后端在has_perm()has_module_perms()中引发PermissionDenied异常,则授权将立即失败,Django 不会检查后续的后端。

后端可以像这样实现神奇管理员的权限

from django.contrib.auth.backends import BaseBackend


class MagicAdminBackend(BaseBackend):
    def has_perm(self, user_obj, perm, obj=None):
        return user_obj.username == settings.ADMIN_LOGIN

这为在上例中获得访问权限的用户提供了完全权限。请注意,除了提供给关联的django.contrib.auth.models.User函数的相同参数外,后端身份验证函数都接受用户对象(可能是匿名用户)作为参数。

可以在django/contrib/auth/backends.py中的ModelBackend类中找到完整的授权实现,它是默认后端,大多数情况下会查询auth_permission表。

匿名用户的授权

匿名用户是指未经身份验证的用户,即他们未提供有效的身份验证信息。但是,这并不一定意味着他们无权执行任何操作。在最基本的层面上,大多数网站都允许匿名用户浏览大部分站点内容,许多网站还允许匿名发布评论等。

Django 的权限框架没有地方存储匿名用户的权限。但是,传递给身份验证后端的 user 对象可能是 django.contrib.auth.models.AnonymousUser 对象,允许后端为匿名用户指定自定义授权行为。这对于可重用应用程序的作者尤其有用,他们可以将所有授权问题委托给身份验证后端,而无需例如通过设置来控制匿名访问。

非活动用户的授权

非活动用户是指其 is_active 字段设置为 False 的用户。ModelBackendRemoteUserBackend 身份验证后端禁止这些用户进行身份验证。如果自定义用户模型没有 is_active 字段,则所有用户都将被允许进行身份验证。

如果要允许非活动用户进行身份验证,可以使用 AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend

权限系统中对匿名用户的支持允许出现这种情况:匿名用户拥有执行某些操作的权限,而非活动的已认证用户则没有。

不要忘记在您自己的后端权限方法中测试用户的 is_active 属性。

处理对象权限

Django 的权限框架具有对象权限的基础,但核心部分没有实现它。这意味着检查对象权限将始终返回 False 或空列表(取决于执行的检查)。身份验证后端将为每个与对象相关的授权方法接收关键字参数 objuser_obj,并可以根据需要返回对象级别的权限。

自定义权限

要为给定的模型对象创建自定义权限,请使用 permissions 模型 Meta 属性

此示例 Task 模型创建了两个自定义权限,即用户可以或不可以对 Task 实例执行的操作,特定于您的应用程序。

class Task(models.Model):
    ...

    class Meta:
        permissions = [
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        ]

这唯一的作用是在运行 manage.py migrate(创建权限的函数连接到 post_migrate 信号)时创建这些额外的权限。当用户尝试访问应用程序提供的功能(更改任务状态或关闭任务)时,您的代码负责检查这些权限的值。继续上面的示例,以下代码检查用户是否可以关闭任务:

user.has_perm("app.close_task")

扩展现有的 User 模型

有两种方法可以扩展默认的 User 模型,而无需替换您自己的模型。如果您需要的更改纯粹是行为上的,并且不需要更改数据库中存储的内容,您可以基于 User 创建一个 代理模型。这允许使用代理模型提供的任何功能,包括默认排序、自定义管理器或自定义模型方法。

如果您希望存储与 User 相关的的信息,您可以使用 OneToOneField 连接到包含附加信息字段的模型。这个一对一模型通常被称为 profile 模型,因为它可能存储与身份验证无关的站点用户信息。例如,您可以创建一个 Employee 模型:

from django.contrib.auth.models import User


class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    department = models.CharField(max_length=100)

假设存在一个名为 Fred Smith 的员工,他同时拥有 User 和 Employee 模型,您可以使用 Django 的标准关联模型约定访问相关信息:

>>> u = User.objects.get(username="fsmith")
>>> freds_department = u.employee.department

要在管理员的用户页面中添加 profile 模型的字段,请在应用程序的 admin.py 中定义一个 InlineModelAdmin(对于此示例,我们将使用 StackedInline),并将其添加到与 User 类注册的 UserAdmin 类中:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User

from my_user_profile_app.models import Employee


# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
    model = Employee
    can_delete = False
    verbose_name_plural = "employee"


# Define a new User admin
class UserAdmin(BaseUserAdmin):
    inlines = [EmployeeInline]


# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

这些 profile 模型没有任何特殊之处——它们只是碰巧与用户模型有一对一链接的 Django 模型。因此,它们在创建用户时不会自动创建,但可以使用 django.db.models.signals.post_save 来根据需要创建或更新相关模型。

使用关联模型会导致额外的查询或连接来检索相关数据。根据您的需求,包含相关字段的自定义用户模型可能是更好的选择,但是,项目应用程序中与默认用户模型的现有关系可能会证明额外的数据库负载是合理的。

替换自定义 User 模型

某些类型的项目可能具有 Django 内置的 User 模型并不总是合适的身份验证要求。例如,在某些网站上,使用电子邮件地址作为您的标识令牌比使用用户名更有意义。

Django 允许您通过为 AUTH_USER_MODEL 设置提供一个引用自定义模型的值来覆盖默认用户模型:

AUTH_USER_MODEL = "myapp.MyUser"

此点对描述了 Django 应用程序的 label(必须位于您的 INSTALLED_APPS 中)以及您希望用作用户模型的 Django 模型的名称。

在启动项目时使用自定义用户模型

如果您要启动一个新项目,您可以通过子类化 AbstractUser 来设置一个行为与默认用户模型相同的自定义用户模型:

from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass

不要忘记将 AUTH_USER_MODEL 指向它。在创建任何迁移或第一次运行 manage.py migrate 之前执行此操作。

此外,在应用程序的 admin.py 中注册模型:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

在项目中途更改为自定义用户模型

在创建数据库表后更改 AUTH_USER_MODEL 是可能的,但可能很复杂,因为它会影响外键和多对多关系等。

此更改无法自动完成,需要手动修复模式,将数据从旧用户表移动,并可能手动重新应用一些迁移。有关步骤概述,请参见 #25313

由于Django的可交换模型动态依赖特性存在限制,由AUTH_USER_MODEL引用的模型必须在其应用的第一次迁移(通常称为0001_initial)中创建;否则,将会出现依赖问题。

此外,在运行迁移时,您可能会遇到CircularDependencyError错误,因为Django无法自动打破动态依赖导致的循环依赖。如果看到此错误,您应该通过将用户模型依赖的模型移动到第二次迁移来打破循环。(您可以尝试创建两个具有相互ForeignKey关系的普通模型,看看makemigrations如何解决该循环依赖,如果您想了解通常是如何完成的。)

可复用应用和AUTH_USER_MODEL

可复用应用不应该实现自定义用户模型。一个项目可能使用许多应用,而两个实现了自定义用户模型的可复用应用无法一起使用。如果您需要在您的应用中存储每个用户的用户信息,请使用ForeignKeyOneToOneField关联到settings.AUTH_USER_MODEL,如下所述。

引用User模型

如果您直接引用User(例如,在外部键中引用它),您的代码将无法在AUTH_USER_MODEL设置已更改为其他用户模型的项目中工作。

get_user_model()[source]

不要直接引用User,而应使用django.contrib.auth.get_user_model()引用用户模型。此方法将返回当前活动的用户模型——如果指定了自定义用户模型,则返回自定义用户模型;否则,返回User

当您定义与用户模型的外部键或多对多关系时,您应该使用AUTH_USER_MODEL设置指定自定义模型。例如:

from django.conf import settings
from django.db import models


class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )

当连接用户模型发送的信号时,您应该使用AUTH_USER_MODEL设置指定自定义模型。例如:

from django.conf import settings
from django.db.models.signals import post_save


def post_save_receiver(sender, instance, created, **kwargs):
    pass


post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)

一般来说,在导入时执行的代码中,使用AUTH_USER_MODEL设置引用用户模型最简单,但是,在Django导入模型时也可以调用get_user_model(),因此您可以使用models.ForeignKey(get_user_model(), ...)

如果您的应用程序使用多个用户模型进行测试(例如,使用@override_settings(AUTH_USER_MODEL=...)),并且您在模块级变量中缓存了get_user_model()的结果,您可能需要监听setting_changed信号来清除缓存。例如:

from django.apps import apps
from django.contrib.auth import get_user_model
from django.core.signals import setting_changed
from django.dispatch import receiver


@receiver(setting_changed)
def user_model_swapped(*, setting, **kwargs):
    if setting == "AUTH_USER_MODEL":
        apps.clear_cache()
        from myapp import some_module

        some_module.UserModel = get_user_model()

指定自定义用户模型

当您使用自定义用户模型启动项目时,请考虑这是否是您项目的正确选择。

将所有用户相关信息保存在一个模型中,无需进行额外或更复杂的数据库查询来检索相关模型。另一方面,将特定于应用程序的用户信息存储在一个与您的自定义用户模型相关的模型中可能更合适。这允许每个应用程序指定其自己的用户数据需求,而不会与其他应用程序的潜在冲突或破坏性假设相冲突。这也意味着您可以使您的用户模型尽可能简单,专注于身份验证,并遵循Django期望自定义用户模型满足的最低要求。

如果您使用默认的身份验证后端,则您的模型必须具有一个可用于标识的唯一字段。这可以是用户名、电子邮件地址或任何其他唯一属性。如果您使用可以支持它的自定义身份验证后端,则允许使用非唯一用户名字段。

构建符合要求的自定义用户模型最简单的方法是从AbstractBaseUser继承。AbstractBaseUser提供了用户模型的核心实现,包括哈希密码和令牌化的密码重置。然后,您必须提供一些关键的实现细节

class models.CustomUser
USERNAME_FIELD

一个字符串,描述用作唯一标识符的用户模型上字段的名称。这通常是某种用户名,但也可能是电子邮件地址或任何其他唯一标识符。除非您使用可以支持非唯一用户名的自定义身份验证后端,否则该字段*必须*是唯一的(例如,在其定义中设置了unique=True)。

在下面的示例中,字段identifier用作标识字段

class MyUser(AbstractBaseUser):
    identifier = models.CharField(max_length=40, unique=True)
    ...
    USERNAME_FIELD = "identifier"
EMAIL_FIELD

一个字符串,描述User模型上电子邮件字段的名称。此值由get_email_field_name()返回。

REQUIRED_FIELDS

一个字段名称列表,在通过createsuperuser管理命令创建用户时将提示输入这些字段的值。系统会提示用户为这些字段中的每一个提供值。它必须包含blankFalse或未定义的任何字段,并且可以包含在交互式创建用户时要提示的附加字段。REQUIRED_FIELDS在Django的其他部分(如在管理界面创建用户)中没有效果。

例如,这是一个用户模型的部分定义,它定义了两个必填字段——出生日期和身高

class MyUser(AbstractBaseUser):
    ...
    date_of_birth = models.DateField()
    height = models.FloatField()
    ...
    REQUIRED_FIELDS = ["date_of_birth", "height"]

注意

REQUIRED_FIELDS必须包含用户模型上的所有必填字段,但不应包含USERNAME_FIELDpassword,因为这些字段将始终提示输入。

is_active

一个布尔属性,指示用户是否被认为是“活动”的。此属性作为AbstractBaseUser上的属性提供,默认为True。您如何选择实现它将取决于您选择的身份验证后端的细节。有关详细信息,请参阅is_active attribute on the built-in user model的文档。

get_full_name()

可选。用户的较长的正式标识符,例如他们的全名。如果实现,这将与django.contrib.admin中对象的记录中的用户名一起显示。

get_short_name()

可选。用户的简短非正式标识符,例如他们的名字。如果实现此功能,则会在django.contrib.admin的页眉中用它替换用户问候语中的用户名。

导入AbstractBaseUser

可以从django.contrib.auth.base_user导入AbstractBaseUserBaseUserManager,这样就可以在不包含INSTALLED_APPS中的django.contrib.auth的情况下导入它们。

以下属性和方法可用于AbstractBaseUser的任何子类

class models.AbstractBaseUser
get_username()

返回USERNAME_FIELD指定的字段的值。

clean()

通过调用normalize_username()来规范化用户名。如果重写此方法,请务必调用super()以保留规范化。

classmethod get_email_field_name()

返回由EMAIL_FIELD属性指定的电子邮件字段的名称。如果未指定EMAIL_FIELD,则默认为'email'

classmethod normalize_username(username)

将NFKC Unicode规范化应用于用户名,以便具有不同Unicode代码点的视觉上相同的字符被视为相同。

is_authenticated

只读属性,始终为True(与始终为FalseAnonymousUser.is_authenticated相反)。这是一种判断用户是否已通过身份验证的方法。这并不意味着任何权限,也不会检查用户是否处于活动状态或具有有效的会话。即使通常您会在request.user上检查此属性以了解它是否已由AuthenticationMiddleware填充(表示当前登录的用户),您也应该知道此属性对于任何User实例都为True

is_anonymous

只读属性,始终为False。这是一种区分UserAnonymousUser对象的方法。通常,您应该优先使用is_authenticated而不是此属性。

set_password(raw_password)

将用户的密码设置为给定的原始字符串,负责密码哈希。不保存AbstractBaseUser对象。

当raw_password为None时,密码将设置为不可用的密码,就像使用了set_unusable_password()一样。

check_password(raw_password)
acheck_password(raw_password)

异步版本acheck_password()

如果给定的原始字符串是用户的正确密码,则返回True。(这在进行比较时负责密码哈希。)

Django 5.0 中的更改

添加了acheck_password()方法。

set_unusable_password()

将用户标记为未设置密码。这与密码为空字符串不同。check_password()对于此用户将永远不会返回True。不保存AbstractBaseUser对象。

如果应用程序的身份验证针对现有外部源(例如LDAP目录)进行,则可能需要此功能。

has_usable_password()

如果为此用户调用了set_unusable_password(),则返回False

get_session_auth_hash()

返回密码字段的HMAC。用于更改密码时的会话失效

get_session_auth_fallback_hash()

使用SECRET_KEY_FALLBACKS生成密码字段的HMAC。get_user()使用。

AbstractUserAbstractBaseUser的子类

class models.AbstractUser
clean()

通过调用BaseUserManager.normalize_email()来规范化电子邮件。如果重写此方法,请务必调用super()以保留规范化。

为自定义用户模型编写管理器

您还应该为您的用户模型定义一个自定义管理器。如果您的用户模型定义的usernameemailis_staffis_activeis_superuserlast_logindate_joined字段与Django的默认用户相同,您可以安装Django的UserManager;但是,如果您的用户模型定义了不同的字段,则需要定义一个扩展BaseUserManager的自定义管理器,并提供两种附加方法

class models.CustomUserManager
create_user(username_field, password=None, **other_fields)

create_user() 的原型应该接受用户名字段以及所有必需字段作为参数。例如,如果您的用户模型使用 email 作为用户名字段,并且具有 date_of_birth 作为必需字段,则 create_user 应该定义为

def create_user(self, email, date_of_birth, password=None):
    # create user here
    ...
create_superuser(username_field, password=None, **other_fields)

create_superuser() 的原型应该接受用户名字段以及所有必需字段作为参数。例如,如果您的用户模型使用 email 作为用户名字段,并且具有 date_of_birth 作为必需字段,则 create_superuser 应该定义为

def create_superuser(self, email, date_of_birth, password=None):
    # create superuser here
    ...

对于 ForeignKeyUSERNAME_FIELDREQUIRED_FIELDS 中,这些方法接收现有实例的 to_field(默认为 primary_key)的值。

BaseUserManager 提供以下实用程序方法

class models.BaseUserManager
classmethod normalize_email(email)

通过小写化电子邮件地址的域名部分来规范化电子邮件地址。

get_by_natural_key(username)

使用 USERNAME_FIELD 指定的字段内容检索用户实例。

扩展 Django 的默认 User

如果您对 Django 的 User 模型完全满意,但想要添加一些额外的个人资料信息,您可以继承 django.contrib.auth.models.AbstractUser 并添加自定义的个人资料字段,尽管我们建议使用 指定自定义用户模型 中描述的单独模型。AbstractUser 将默认 User 的完整实现作为 抽象模型 提供。

自定义用户和内置身份验证表单

Django 的内置 表单视图 对其使用的用户模型做出了某些假设。

以下表单与 AbstractBaseUser 的任何子类兼容

以下表单对用户模型做出了假设,如果满足这些假设,则可以按原样使用

  • PasswordResetForm:假设用户模型具有一个字段,该字段使用 get_email_field_name()(默认为 email)返回的名称存储用户的电子邮件地址,该地址可用于识别用户,以及一个名为 is_active 的布尔字段,以防止对非活动用户的密码重置。

最后,以下表单与 User 绑定,需要重写或扩展才能与自定义用户模型一起使用

如果您的自定义用户模型是 AbstractUser 的子类,则可以按这种方式扩展这些表单

from django.contrib.auth.forms import UserCreationForm
from myapp.models import CustomUser


class CustomUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = CustomUser
        fields = UserCreationForm.Meta.fields + ("custom_field",)

自定义用户和 django.contrib.admin

如果您希望您的自定义用户模型也能与管理员一起使用,则您的用户模型必须定义一些附加属性和方法。这些方法允许管理员控制用户访问管理员内容的权限。

class models.CustomUser
is_staff

如果允许用户访问管理员站点,则返回 True

is_active

如果用户帐户当前处于活动状态,则返回 True

has_perm(perm, obj=None):

如果用户拥有指定的权限,则返回 True。如果提供了 obj,则需要针对特定的对象实例检查权限。

has_module_perms(app_label)

如果用户有权访问给定应用中的模型,则返回 True

您还需要将自定义用户模型注册到管理员。如果您的自定义用户模型扩展了 django.contrib.auth.models.AbstractUser,则可以使用 Django 现有的 django.contrib.auth.admin.UserAdmin 类。但是,如果您的用户模型扩展了 AbstractBaseUser,则需要定义一个自定义的 ModelAdmin 类。可以继承默认的 django.contrib.auth.admin.UserAdmin;但是,您需要覆盖任何引用 django.contrib.auth.models.AbstractUser 上但在您的自定义用户类上不存在的字段的定义。

注意

如果您使用的是自定义 ModelAdmin(它是 django.contrib.auth.admin.UserAdmin 的子类),则需要将自定义字段添加到 fieldsets(用于编辑用户的字段)和 add_fieldsets(用于创建用户时使用的字段)。例如

from django.contrib.auth.admin import UserAdmin


class CustomUserAdmin(UserAdmin):
    ...
    fieldsets = UserAdmin.fieldsets + ((None, {"fields": ["custom_field"]}),)
    add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ["custom_field"]}),)

有关更多详细信息,请参阅 完整示例

自定义用户和权限

为了方便地将Django的权限框架集成到您自己的用户类中,Django 提供了 PermissionsMixin。这是一个抽象模型,您可以将其包含在您的用户模型的类层次结构中,从而获得支持Django权限模型所需的所有方法和数据库字段。

PermissionsMixin 提供以下方法和属性:

class models.PermissionsMixin
is_superuser

布尔值。指定此用户拥有所有权限,无需显式分配。

get_user_permissions(obj=None)

返回用户直接拥有的权限字符串集合。

如果传入obj,则仅返回此特定对象的用户的权限。

get_group_permissions(obj=None)

返回用户通过其组拥有的权限字符串集合。

如果传入obj,则仅返回此特定对象的组权限。

get_all_permissions(obj=None)

返回用户拥有的权限字符串集合,包括组权限和用户权限。

如果传入obj,则仅返回此特定对象的权限。

has_perm(perm, obj=None)

如果用户拥有指定的权限,则返回True,其中perm的格式为"<app label>.<permission codename>"(参见 permissions)。如果User.is_activeis_superuser都为True,则此方法始终返回True

如果传入obj,此方法不会检查模型的权限,而是检查此特定对象的权限。

has_perms(perm_list, obj=None)

如果用户拥有每个指定的权限,则返回True,其中每个perm的格式为"<app label>.<permission codename>"。如果User.is_activeis_superuser都为True,则此方法始终返回True

如果传入obj,此方法不会检查模型的权限,而是检查特定对象的权限。

has_module_perms(package_name)

如果用户在给定的包(Django应用标签)中拥有任何权限,则返回True。如果User.is_activeis_superuser都为True,则此方法始终返回True

PermissionsMixinModelBackend

如果您不包含 PermissionsMixin,则必须确保不调用ModelBackend上的权限方法。ModelBackend 假设您的用户模型上提供某些字段。如果您的用户模型未提供这些字段,则在检查权限时会收到数据库错误。

自定义用户和代理模型

自定义用户模型的一个限制是,安装自定义用户模型会破坏任何扩展 User 的代理模型。代理模型必须基于具体的基类;通过定义自定义用户模型,您将删除Django可靠识别基类功能。

如果您的项目使用代理模型,则必须修改代理以扩展项目中使用的用户模型,或者将代理的行为合并到您的 User 子类中。

完整示例

这是一个符合管理员规范的自定义用户应用程序的示例。此用户模型使用电子邮件地址作为用户名,并具有必需的出生日期;它除了用户帐户上的admin标志外,不提供任何权限检查。此模型将与所有内置身份验证表单和视图兼容,但用户创建表单除外。此示例说明了大多数组件如何协同工作,但并非旨在直接复制到项目中以供生产使用。

此代码将全部位于自定义身份验证应用程序的models.py文件中。

from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser


class MyUserManager(BaseUserManager):
    def create_user(self, email, date_of_birth, password=None):
        """
        Creates and saves a User with the given email, date of
        birth and password.
        """
        if not email:
            raise ValueError("Users must have an email address")

        user = self.model(
            email=self.normalize_email(email),
            date_of_birth=date_of_birth,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, date_of_birth, password=None):
        """
        Creates and saves a superuser with the given email, date of
        birth and password.
        """
        user = self.create_user(
            email,
            password=password,
            date_of_birth=date_of_birth,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user


class MyUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name="email address",
        max_length=255,
        unique=True,
    )
    date_of_birth = models.DateField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = MyUserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["date_of_birth"]

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

然后,要使用Django的管理员注册此自定义用户模型,需要在应用程序的admin.py文件中添加以下代码。

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError

from customauth.models import MyUser


class UserCreationForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""

    password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
    password2 = forms.CharField(
        label="Password confirmation", widget=forms.PasswordInput
    )

    class Meta:
        model = MyUser
        fields = ["email", "date_of_birth"]

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user


class UserChangeForm(forms.ModelForm):
    """A form for updating users. Includes all the fields on
    the user, but replaces the password field with admin's
    disabled password hash display field.
    """

    password = ReadOnlyPasswordHashField()

    class Meta:
        model = MyUser
        fields = ["email", "password", "date_of_birth", "is_active", "is_admin"]


class UserAdmin(BaseUserAdmin):
    # The forms to add and change user instances
    form = UserChangeForm
    add_form = UserCreationForm

    # The fields to be used in displaying the User model.
    # These override the definitions on the base UserAdmin
    # that reference specific fields on auth.User.
    list_display = ["email", "date_of_birth", "is_admin"]
    list_filter = ["is_admin"]
    fieldsets = [
        (None, {"fields": ["email", "password"]}),
        ("Personal info", {"fields": ["date_of_birth"]}),
        ("Permissions", {"fields": ["is_admin"]}),
    ]
    # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
    # overrides get_fieldsets to use this attribute when creating a user.
    add_fieldsets = [
        (
            None,
            {
                "classes": ["wide"],
                "fields": ["email", "date_of_birth", "password1", "password2"],
            },
        ),
    ]
    search_fields = ["email"]
    ordering = ["email"]
    filter_horizontal = []


# Now register the new UserAdmin...
admin.site.register(MyUser, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)

最后,使用settings.py中的 AUTH_USER_MODEL 设置指定自定义模型作为项目的默认用户模型。

AUTH_USER_MODEL = "customauth.MyUser"
返回顶部