在 Django 中自定义身份验证 — Django 文档

来自菜鸟教程
Django/docs/3.1.x/topics/auth/customizing
跳转至:导航、​搜索

在 Django 中自定义身份验证

Django 附带的身份验证对于大多数常见情况来说已经足够好了,但是开箱即用的默认设置可能无法满足您的需求。 在您的项目中自定义身份验证需要了解所提供系统的哪些点是可扩展或可替换的。 本文档提供有关如何自定义身份验证系统的详细信息。

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

你可以给你的模型 自定义权限 可以通过 Django 的授权系统检查。

您可以扩展默认的User模型,或替换完全定制的模型。

其他认证来源

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

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

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

有关 Django 包含的身份验证后端的信息,请参阅 身份验证后端参考

指定身份验证后端

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

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

默认情况下,:setting:`AUTHENTICATION_BACKENDS` 设置为:

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

这是检查 Django 用户数据库并查询内置权限的基本身份验证后端。 它不通过任何速率限制机制提供针对蛮力攻击的保护。 您可以在自定义身份验证后端中实现自己的速率限制机制,也可以使用大多数 Web 服务器提供的机制。

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

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

笔记

一旦用户通过身份验证,Django 会存储在用户会话中用于验证用户身份的后端,并在需要访问当前身份验证的用户时在该会话期间重新使用相同的后端。 这实际上意味着身份验证源是基于每个会话缓存的,因此如果您更改 :setting:`AUTHENTICATION_BACKENDS`,如果您需要强制用户重新设置,则需要清除会话数据使用不同的方法进行身份验证。 一个简单的方法是执行 Session.objects.all().delete()


编写身份验证后端

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

get_user 方法接受一个 user_id——它可以是用户名、数据库 ID 或其他任何东西,但必须是你的用户对象的主键——并返回一个用户对象或 [ X178X]。

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

request 是一个 HttpRequest,如果没有提供给 authenticate()(将它传递给后端),它可能是 None

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

这是一个示例后端,它根据您的 settings.py 文件中定义的用户名和密码变量进行身份验证,并在用户第一次进行身份验证时创建一个 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)
                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(), [[Django/docs/3.1.x/ref/contrib/auth#django.contrib.auth.models.User|]]has_perm7(X) ]、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 函数的相同参数之外,后端 auth 函数都将用户对象(可能是匿名用户)作为参数。

完整的授权实现可以在:source:`django/contrib/auth/backends.py`ModelBackend类中找到,它是默认后端,查询[ X176X] 表大部分时间。

匿名用户授权

匿名用户是未经身份验证的用户,即 他们没有提供有效的身份验证详细信息。 然而,这并不一定意味着他们无权做任何事情。 在最基本的层面上,大多数网站都授权匿名用户浏览网站的大部分内容,并且许多网站允许匿名发表评论等。

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


非活动用户的授权

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

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

权限系统中对匿名用户的支持允许匿名用户有权限做某事,而非活动的经过身份验证的用户则没有。

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


处理对象权限

Django 的权限框架具有对象权限的基础,尽管在核心中没有实现。 这意味着检查对象权限将始终返回 False 或空列表(取决于执行的检查)。 认证后端将接收每个对象相关授权方法的关键字参数objuser_obj,并可以适当地返回对象级别的权限。


自定义权限

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

此示例 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"),
        ]

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

user.has_perm('app.close_task')

扩展现有的 User 模型

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

如果您希望存储与 User 相关的信息,您可以将 OneToOneField 用于包含附加信息字段的模型。 这种一对一模型通常称为配置文件模型,因为它可能存储有关站点用户的非身份验证相关信息。 例如,您可以创建一个 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)

假设现有 Employee Fred Smith 拥有 User 和 Employee 模型,您可以使用 Django 的标准相关模型约定访问相关信息:

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

要将配置文件模型的字段添加到管理员中的用户页面,请在您的应用程序的 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)

这些配置文件模型并没有什么特别之处——它们只是 Django 模型,碰巧与用户模型具有一对一的链接。 因此,它们不会在创建用户时自动创建,但可以使用 django.db.models.signals.post_save 来创建或更新相关模型。

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


替换自定义 User 模型

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

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

AUTH_USER_MODEL = 'myapp.MyUser'

这对点对描述了 Django 应用程序的名称(必须在您的 :setting:`INSTALLED_APPS` 中),以及您希望用作用户模型的 Django 模型的名称。

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

如果您正在开始一个新项目,强烈建议设置自定义用户模型,即使默认的 User 模型对您来说已经足够了。 此模型的行为与默认用户模型相同,但您将来可以在需要时对其进行自定义:

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

不要忘记将 :setting:`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)

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

例如,在创建数据库表后更改 :setting:`AUTH_USER_MODEL` 明显困难得多,因为它会影响外键和多对多关系。

此更改无法自动完成,需要手动修复架构、从旧用户表中移动数据,并可能手动重新应用某些迁移。 有关步骤的概述,请参阅 :ticket:`25313`

由于 Django 对可交换模型的动态依赖特性的限制,:setting:`AUTH_USER_MODEL` 引用的模型必须在其应用程序的第一次迁移中创建(通常称为 0001_initial); 否则,您将遇到依赖性问题。

此外,在运行迁移时可能会遇到 CircularDependencyError,因为 Django 将无法由于动态依赖而自动中断依赖循环。 如果您看到此错误,您应该通过将用户模型依赖的模型移动到第二次迁移来打破循环。 (您可以尝试制作两个彼此具有 ForeignKey 的普通模型,并查看 makemigrations 如何解决这种循环依赖,如果您想了解它通常是如何完成的。)


可重复使用的应用程序和 AUTH_USER_MODEL

可重用应用不应实现自定义用户模型。 一个项目可能使用多个应用程序,两个实现自定义用户模型的可重用应用程序不能一起使用。 如果您需要在应用中存储每个用户的信息,请使用 ForeignKeyOneToOneFieldsettings.AUTH_USER_MODEL,如下所述。


参考 User 型号

如果您直接引用 User(例如,通过在外键中引用它),您的代码将无法在 :setting:`AUTH_USER_MODEL` 设置已更改的项目中工作到不同的用户模型。

get_user_model()

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

当您为用户模型定义外键或多对多关系时,您应该使用 :setting:`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,
    )

当连接到用户模型发送的信号时,您应该使用 :setting:`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)

一般来说,在导入时执行的代码中使用 :setting:`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(**kwargs):
    if kwargs['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

通过 :djadmin:`createsuperuser` 管理命令创建用户时将提示输入的字段名称列表。 系统将提示用户为这些字段中的每一个提供一个值。 它必须包括 blankFalse 或未定义的任何字段,并且可能包括您希望在交互式创建用户时提示的其他字段。 REQUIRED_FIELDS 在 Django 的其他部分没有影响,比如在 admin 中创建用户。

3.0 版新功能:REQUIRED_FIELDS 现在支持 ManyToManyFields,无需自定义直通模型。 由于在 :djadmin:`createsuperuser` 提示期间无法传递模型实例,因此希望用户输入与模型相关的类的现有实例的 ID。

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

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

笔记

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

is_active

指示用户是否被视为“活跃”的布尔属性。 此属性作为 AbstractBaseUser 上的属性提供,默认为 True。 您选择如何实施它取决于您选择的身份验证后端的详细信息。 有关详细信息,请参阅内置用户模型 上的 is_active 属性的文档。

get_full_name()

可选的。 用户的更长的正式标识符,例如他们的全名。 如果实施,这会出现在 django.contrib.admin 中对象历史记录中的用户名旁边。

get_short_name()

可选的。 用户的简短、非正式的标识符,例如他们的名字。 如果实现,这将替换 django.contrib.admin 标题中向用户致意的用户名。

导入 AbstractBaseUser

AbstractBaseUserBaseUserManager 可从 django.contrib.auth.base_user 导入,因此它们可以在 :setting:`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(相对于 AnonymousUser.is_authenticated,始终为 False)。 这是一种判断用户是否已通过身份验证的方法。 这并不意味着任何权限,也不检查用户是否处于活动状态或具有有效的会话。 即使通常您会在 request.user 上检查此属性以了解它是否已被 AuthenticationMiddleware(代表当前登录的用户)填充,但您应该知道此属性是 [X226X ] 用于任何 User 实例。

is_anonymous

只读属性,始终为 False。 这是区分 UserAnonymousUser 对象的一种方式。 通常,您应该更喜欢使用 is_authenticated 来代替此属性。

set_password(raw_password)

将用户的密码设置为给定的原始字符串,处理密码散列。 不保存 AbstractBaseUser 对象。

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

check_password(raw_password)

如果给定的原始字符串是用户的正确密码,则返回 True。 (这会在进行比较时处理密码散列。)

set_unusable_password()

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

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

has_usable_password()

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

get_session_auth_hash()

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

3.1 版更改: 哈希算法更改为 SHA-256。

AbstractUser 子类 AbstractBaseUser

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
    ...

对于 USERNAME_FIELD 或 REQUIRED_FIELDS 中的 ForeignKey,这些方法接收 to_field 的值(primary_key] default) 的现有实例。

BaseUserManager 提供以下实用方法:

class models.BaseUserManager
classmethod normalize_email(email)

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

get_by_natural_key(username)

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

make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')

返回具有给定长度和给定允许字符字符串的随机密码。 请注意,allowed_chars 的默认值不包含可能导致用户混淆的字母,包括:

  • ilI1(小写字母i、小写字母L、大写字母i和数字一)

  • oO0(小写字母 o、大写字母 o 和零)


扩展 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)

3.0 版中的新功能。

返回用户直接拥有的一组权限字符串。

如果传入 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,其中每个权限的格式为 "<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
    password hash display field.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_admin')

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]


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 中的 :setting:`AUTH_USER_MODEL` 设置将自定义模型指定为项目的默认用户模型:

AUTH_USER_MODEL = 'customauth.MyUser'