自定义 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 会将用于验证该用户的后端信息存储在用户的会话(session)中,并在该会话期间需要访问当前已验证用户时重复使用该后端。这意味着身份验证来源是按会话进行缓存的。因此,如果你修改了 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。
request 是一个 HttpRequest 对象,如果调用 authenticate() 时未提供该参数(它会将该参数传递给后端),则可能为 None。
Django 管理后台与 Django 用户对象紧密耦合。例如,用户要访问管理后台,User.is_staff 和 User.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
自定义权限¶
要为给定的模型对象创建自定义权限,请使用 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
要将配置文件模型的字段添加到管理后台的用户页面,在应用的 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 在适当时创建或更新相关模型。
使用相关模型会导致额外的查询或连接(join)以检索相关数据。根据你的需求,包含相关字段的自定义用户模型可能是更好的选择;然而,项目中针对默认用户模型的现有关系可能证明了额外数据库负载的合理性。
替换自定义 User 模型¶
某些类型的项目可能存在 Django 内置的 User 模型不太适用的身份验证需求。例如,在某些站点上,使用电子邮件地址而不是用户名作为标识令牌更有意义。
Django 允许你通过为引用自定义模型的 AUTH_USER_MODEL 设置提供一个值来覆盖默认用户模型:
AUTH_USER_MODEL = "myapp.MyUser"
这个点分对描述了 Django 应用的 标签(必须在你的 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 对可替换模型(swappable models)的动态依赖特性存在限制,AUTH_USER_MODEL 引用的模型必须在其应用的第一次迁移中创建(通常称为 0001_initial);否则,你将会遇到依赖问题。
此外,在运行迁移时,你可能会遇到 CircularDependencyError,因为由于动态依赖,Django 无法自动打破依赖循环。如果你看到此错误,应该通过将用户模型所依赖的模型移动到第二个迁移中来打破循环。(如果你想看看通常是怎么做的,可以尝试创建两个互为 ForeignKey 的普通模型,看看 makemigrations 如何解析这种循环依赖。)
可复用应用与 AUTH_USER_MODEL¶
可复用应用不应实现自定义用户模型。一个项目可能会使用许多应用,如果两个实现自定义用户模型的应用同时使用,它们将无法一起工作。如果你需要在应用中存储每个用户的信息,请使用指向 settings.AUTH_USER_MODEL 的 ForeignKey 或 OneToOneField,如下所述。
引用 User 模型¶
如果你直接引用 User(例如,通过在外键中引用它),你的代码在 AUTH_USER_MODEL 设置已更改为其他用户模型的项目中将无法正常工作。
- get_user_model()[source]¶
你应该使用
django.contrib.auth.get_user_model()来引用用户模型,而不是直接引用User。此方法将返回当前活动的模型 —— 如果指定了自定义用户模型则返回该模型,否则返回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管理命令创建用户时,将被提示输入的字段名列表。用户将被提示为其中每个字段提供值。它必须包括所有blank为False或未定义的字段,并可以包括你在交互式创建用户时希望被提示输入的额外字段。REQUIRED_FIELDS对 Django 的其他部分(如在管理后台中创建用户)没有影响。例如,这里是一个定义了两个必填字段(出生日期和身高)的用户模型的部分定义:
class MyUser(AbstractBaseUser): ... date_of_birth = models.DateField() height = models.FloatField() ... REQUIRED_FIELDS = ["date_of_birth", "height"]
注意
REQUIRED_FIELDS必须包含用户模型上的所有必填字段,但*不应*包含USERNAME_FIELD或password,因为这些字段总是会被提示输入。
- is_active¶
一个布尔属性,指示用户是否被视为“活动”。此属性作为
AbstractBaseUser上的一个属性提供,默认为True。你如何实现它将取决于你所选择的身份验证后端的细节。详情请参阅内置用户模型上的 is_active 属性文档。
- get_full_name()¶
可选。用户的更长形式标识符,例如他们的全名。如果实现了此方法,它会出现在
django.contrib.admin中对象历史记录的用户名旁边。
- get_short_name()¶
可选。用户的短形式、非正式标识符,例如他们的名字。如果实现了此方法,它会在
django.contrib.admin页眉中对用户的问候语中替换用户名。
导入
AbstractBaseUserAbstractBaseUser和BaseUserManager可从django.contrib.auth.base_user导入,因此可以在不将django.contrib.auth包含在INSTALLED_APPS中的情况下导入它们。
以下属性和方法在 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(与总是为False的AnonymousUser.is_authenticated相反)。这是判断用户是否已验证的方法。这并不意味着任何权限,也不检查用户是否处于活动状态或具有有效会话。虽然通常会在request.user上检查此属性以找出它是否已被AuthenticationMiddleware填充(代表当前已登录的用户),但你应该知道对于任何User实例,此属性都为True。
- is_anonymous¶
只读属性,总是为
False。这是区分User和AnonymousUser对象的一种方法。通常,你应该优先使用is_authenticated而非此属性。
- set_password(raw_password)¶
将用户的密码设置为给定的原始字符串,并处理密码哈希。不会保存
AbstractBaseUser对象。当 raw_password 为
None时,密码将被设置为不可用的密码,就像使用了set_unusable_password()一样。
- check_password(raw_password)¶
- acheck_password(raw_password)¶
异步版本:
acheck_password()如果给定的原始字符串是用户的正确密码,则返回
True。(这在进行比较时会处理密码哈希。)
- 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()使用。
AbstractUser 继承自 AbstractBaseUser
- class models.AbstractUser¶
- clean()¶
通过调用
BaseUserManager.normalize_email()来标准化电子邮件。如果你覆盖此方法,请确保调用super()以保留标准化。
为自定义用户模型编写管理器¶
你也应该为你的用户模型定义一个自定义管理器。如果你的用户模型定义了与 Django 默认用户相同的 username、email、is_staff、is_active、is_superuser、last_login 和 date_joined 字段,你可以安装 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)的值。
BaseUserManager 提供了以下工具方法:
扩展 Django 的默认 User¶
如果你完全满意 Django 的 User 模型,但想添加一些额外的配置信息,你可以继承 django.contrib.auth.models.AbstractUser 并添加自定义配置字段,尽管我们建议像指定自定义用户模型中描述的那样使用单独的模型。AbstractUser 以抽象模型的形式提供了默认 User 的完整实现。
自定义用户与内置认证表单¶
Django 的内置表单和视图对其所使用的用户模型做了一些假设。
以下表单与 AbstractBaseUser 的任何子类兼容:
AuthenticationForm:使用由USERNAME_FIELD指定的用户名字段。
以下表单对用户模型做了假设,如果这些假设满足,则可以原样使用:
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 上有但你的自定义用户类上没有的字段的定义。
注意
如果你使用作为 django.contrib.auth.admin.UserAdmin 子类的自定义 ModelAdmin,则需要将自定义字段添加到 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>"(参见 权限)。如果User.is_active和is_superuser均为True,则此方法始终返回True。如果传入了
obj,此方法将不会检查模型的权限,而是检查该特定对象的权限。
- has_perms(perm_list, obj=None)¶
如果用户拥有所有指定的权限,则返回
True。每个权限的格式均为"<app label>.<permission codename>"。如果User.is_active和is_superuser均为True,则此方法始终返回True。如果传入了
obj,此方法将不会检查模型的权限,而是检查该特定对象的权限。
- has_module_perms(package_name)¶
如果用户在给定包(Django 应用标签)中拥有任何权限,则返回
True。如果User.is_active和is_superuser均为True,则此方法始终返回True。
自定义用户和代理模型¶
自定义用户模型的一个局限性在于,安装自定义用户模型会破坏任何扩展自 User 的代理模型。代理模型必须基于具体的基类;通过定义自定义用户模型,您移除了 Django 可靠识别基类的能力。
如果您的项目使用了代理模型,则必须修改该代理以扩展项目中正在使用的用户模型,或者将代理的行为合并到您的 User 子类中。
完整示例¶
这是一个兼容管理员(admin)的自定义用户应用示例。该用户模型使用电子邮件地址作为用户名,并具有必需的出生日期字段;除了用户帐户上的 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"
添加异步接口¶
为了优化从异步上下文进行身份验证时的性能,后端可以实现每个函数的异步版本——aget_user(user_id) 和 aauthenticate(request, **credentials)。当身份验证后端扩展了 BaseBackend 且未提供这些函数的异步版本时,它们将自动通过 sync_to_async 进行合成。这会带来 性能损耗。
虽然异步接口是可选的,但同步接口始终是必需的。如果实现了异步接口,系统不会自动合成同步接口。
Django 开箱即用的身份验证后端具有原生的异步支持。如果扩展了这些原生后端,请特别注意确保同时修改已修改函数的异步版本。