Customizing authentication in Django

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

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

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

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

Other authentication sources

您可能有时需要挂接到另一个身份验证来源 - 也就是另一个用户名和密码来源或身份验证方法。

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

所以,为了处理这样的情况,Django认证系统可以让你插入其他认证源。 您可以重写Django的默认基于数据库的方案,或者可以与其他系统一起使用默认系统。

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

Specifying authentication backends

在幕后,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不会检查后面的后端。

Note

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

Writing an authentication backend

一个认证的后端是一个类,该类有两个方法需要实现: get_user(user_id)authenticate(request, **credentials) ,以及一组可选的和authorization methods相关的权限。

get_user方法需要一个user_id - 可以是用户名,数据库ID或其他,但必须是用户对象的主键,并返回一个用户对象。

authenticate方法将请求参数和凭证作为关键字参数。 大多数情况下,它看起来像这样:

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

但它也可以验证令牌,如下所示:

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

无论采用哪种方式,authenticate()都应该检查它获取的凭证,并在凭证有效时返回与这些凭证相匹配的用户对象。 如果它们无效,它应该返回None

request是一个HttpRequest,它可能为None 如果没有被提供给authenticate()(该函数将request递到后端)。

Django管理员与Django 用户对象紧密耦合。 处理这个问题的最好方法是为每个存在于后端的用户创建一个Django User对象(例如,在您的LDAP目录,外部SQL数据库等中)。您可以编写一个脚本提前完成此操作,或者您的身份验证方法可以在用户首次登录时执行此操作。

以下是一个示例后端,它会根据settings.py文件中定义的用户名和密码变量进行身份验证,并在用户第一次验证时创建一个Django User对象:

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

class SettingsBackend:
    """
    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
Changed in Django 1.11:

request参数已添加到authenticate(),并且支持不接受它的后端将在Django 2.1中删除。

处理自定义后端中的授权

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

用户模型将委托权限查找函数(get_group_permissions()get_all_permissions()has_perm()has_module_perms())给任何实现这些功能的认证后端。

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

如果后端在has_perm()has_module_perms()中产生PermissionDenied异常,授权将立即失败,Django不会检查后端随后。

上面的简单后端可以相当简单地实现魔术管理员的权限:

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

这给上面例子中授予访问权限的用户提供了完全的权限。 请注意,除了为相关的django.contrib.auth.models.User函数提供的相同参数之外,后端的auth函数都会将用户对象(可能是匿名用户)作为参数。

完整的授权实现可以在django/contrib/auth/backends.pyModelBackend类中找到,它是默认的后端,并大部分时间都用来查询auth_permission表。 如果您希望仅为后端API的一部分提供自定义行为,则可以利用Python继承和子类ModelBackend,而不是在自定义后端中实现完整的API。

Authorization for anonymous users

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

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

Authorization for inactive users

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

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

对权限系统中的匿名用户的支持允许匿名用户有权执行某些操作,而未活动的经过身份验证的用户则不能这样做。

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

Handling object permissions

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

Custom permissions

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

此示例任务模型创建三个自定义权限,即用户可以或不可以对任务实例执行的操作,具体针对您的应用程序:

class Task(models.Model):
    ...
    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
            ("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.view_task')

Extending the existing User model

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

如果您希望存储与User相关的信息,则可以使用OneToOneField到包含其他信息字段的模型。 这种一对一模式通常称为配置文件模型,因为它可能存储有关站点用户的非auth相关信息。 例如,您可以创建一个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)并将其添加到UserAdmin类,该类已同User类注册:

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来创建或更新相关模型。

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

Substituting a custom User model

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

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

AUTH_USER_MODEL = 'myapp.MyUser'

此点配对表达是Django应用程序的名称(它必须位于您的INSTALLED_APPS中),以及您希望用作用户模型的Django模型的名称。

Using a custom user model when starting a project

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

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)

Changing to a custom user model mid-project

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

此更改不能自动完成,需要手动修复架构,从旧用户表移动数据,并可能需要手动重新应用某些迁移。 See #25313 for an outline of the steps.

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

另外,运行你的迁移时,你可能会碰到一个CircularDependencyError,因为由于动态依赖关系,Django将无法自动打破依赖关系循环。 如果你看到这个错误,你应该通过将你的用户模型所依赖的模型移动到第二次迁移来打破这个循环。 (如果你想看看它是如何实现的,你可以试着制作两个相互拥有ForeignKey的普通模型,并且看看makemigrations如何解决循环依赖问题。)

Reusable apps and AUTH_USER_MODEL

可重用应用程序不应该实现自定义用户模型。 一个项目可能会使用许多应用程序,而实现了自定义用户模型的两个可重用应用程序不能一起使用。 如果您需要将每个用户的信息存储在您的应用中,请按照以下说明使用ForeignKeyOneToOneFieldsettings.AUTH_USER_MODEL

Referencing the User model

如果直接引用User(例如,通过在外键中引用),您的代码在AUTH_USER_MODEL已更改为不同用户模型的项目中不起作用。

get_user_model()[source]

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

在为用户模型定义外键或多对多关系时,应使用AUTH_USER_MODEL设置指定自定义模型。 For example:

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设置指定自定义模型。 For example:

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信号以清除缓存。 For example:

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()
Changed in Django 1.11:

添加了在导入时调用get_user_model()的功能。

Specifying a custom user model

模型设计考虑

在处理与定制用户模型中的身份验证不直接相关的信息之前请仔细考虑。

将应用程序特定的用户信息存储在与用户模型有关系的模型中可能会更好。 这允许每个应用程序指定自己的用户数据需求,而不会冒着与其他应用程序冲突的风险 另一方面,检索这些相关信息的查询将涉及数据库连接,这可能会影响性能。

Django期望您的自定义用户模型能满足一些最低要求。

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

构建兼容自定义用户模型的最简单方法是从AbstractBaseUser继承。 AbstractBaseUser提供用户模型的核心实现,包括散列密码和标记化密码重置。 然后,您必须提供一些关键的实施细节:

class models.CustomUser
USERNAME_FIELD

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

在以下示例中,字段标识符用作标识字段:

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

USERNAME_FIELD现在支持ForeignKey。由于在createsuperuser提示期间无法传递模型实例,因此希望用户输入现有的实例的to_field的值(默认情况下为primary_key )。

EMAIL_FIELD
New in Django 1.11.

A string describing the name of the email field on the User model. This value is returned by get_email_field_name().

REQUIRED_FIELDS

通过createsuperuser管理命令创建用户时将会提示的字段名称列表。 系统会提示用户为每个字段提供一个值。 它必须包含空白False或未定义的任何字段,并且可能包含交互式创建用户时需要提示的其他字段。 REQUIRED_FIELDS在Django的其他部分中没有效果,例如在admin中创建用户。

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

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

Note

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

REQUIRED_FIELDS现在支持ForeignKey。由于在createsuperuser提示期间无法传递模型实例,因此希望用户输入现有的实例的to_field值(默认情况下为primary_key )。

is_active

A boolean attribute that indicates whether the user is considered “active”. This attribute is provided as an attribute on AbstractBaseUser defaulting to True. How you choose to implement it will depend on the details of your chosen auth backends. See the documentation of the is_active attribute on the built-in user model for details.

get_full_name()

Optional. A longer formal identifier for the user such as their full name. If implemented, this appears alongside the username in an object’s history in django.contrib.admin.

get_short_name()

Optional. A short, informal identifier for the user such as their first name. If implemented, this replaces the username in the greeting to the user in the header of django.contrib.admin.

Changed in Django 2.0:

在较早的版本中,AbstractBaseUser的子类需要实现get_short_name()get_full_name()否则会引发NotImplementedError

Importing AbstractBaseUser

AbstractBaseUserBaseUserManager可从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()
New in Django 1.11.

返回由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

Read-only attribute which is always False. This is a way of differentiating User and AnonymousUser objects. Generally, you should prefer using is_authenticated to this attribute.

set_password(raw_password)

Sets the user’s password to the given raw string, taking care of the password hashing. Doesn’t save the AbstractBaseUser object.

When the raw_password is None, the password will be set to an unusable password, as if set_unusable_password() were used.

check_password(raw_password)

Returns True if the given raw string is the correct password for the user. (This takes care of the password hashing in making the comparison.)

set_unusable_password()

Marks the user as having no password set. This isn’t the same as having a blank string for a password. check_password() for this user will never return True. Doesn’t save the AbstractBaseUser object.

You may need this if authentication for your application takes place against an existing external source such as an LDAP directory.

has_usable_password()

Returns False if set_unusable_password() has been called for this user.

get_session_auth_hash()

Returns an HMAC of the password field. Used for Session invalidation on password change.

AbstractUser subclasses AbstractBaseUser:

class models.AbstractUser
clean()
New in Django 1.11.

Normalizes the email by calling BaseUserManager.normalize_email(). If you override this method, be sure to call super() to retain the normalization.

You should also define a custom manager for your user model. If your user model defines username, email, is_staff, is_active, is_superuser, last_login, and date_joined fields the same as Django’s default user, you can just install Django’s UserManager; however, if your user model defines different fields, you’ll need to define a custom manager that extends BaseUserManager providing two additional methods:

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

The prototype of create_user() should accept the username field, plus all required fields as arguments. For example, if your user model uses email as the username field, and has date_of_birth as a required field, then create_user should be defined as:

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

The prototype of create_superuser() should accept the username field, plus all required fields as arguments. For example, if your user model uses email as the username field, and has date_of_birth as a required field, then create_superuser should be defined as:

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

Unlike create_user(), create_superuser() must require the caller to provide a password.

BaseUserManager provides the following utility methods:

class models.BaseUserManager
classmethod normalize_email(email)

Normalizes email addresses by lowercasing the domain portion of the email address.

get_by_natural_key(username)

Retrieves a user instance using the contents of the field nominated by USERNAME_FIELD.

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

Returns a random password with the given length and given string of allowed characters. Note that the default value of allowed_chars doesn’t contain letters that can cause user confusion, including:

  • i, l, I, and 1 (lowercase letter i, lowercase letter L, uppercase letter i, and the number one)
  • o, O, and 0 (lowercase letter o, uppercase letter o, and zero)

Extending Django’s default User

如果你对Django的User模型完全满意,并且你只想添加一些额外的配置文件信息,你可以简单地继承django.contrib.auth.models.AbstractUser并添加您的自定义配置文件字段,尽管我们会建议用一个单独的模型,如指定自定义用户模型的“模型设计注意事项”中所述。 AbstractUser提供了默认的User作为抽象模型的完整实现。

Custom users and the built-in auth forms

Django的内置formsviews对他们正在使用的用户模型做了某些假设。

以下形式与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',)

Custom users and 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上的字段的定义。

Custom users and permissions

为了使Django的权限框架容易包含到你自己的用户类中,Django提供了PermissionsMixin 这是一个抽象模型,可以包含在用户模型的类层次结构中,为您提供支持Django许可模型所需的所有方法和数据库字段。

PermissionsMixin提供以下方法和属性:

class models.PermissionsMixin
is_superuser

Boolean. 指定此用户具有所有权限而不明确分配它们。

get_group_permissions(obj=None)

通过他们的组返回用户拥有的一组权限字符串。

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

get_all_permissions(obj=None)

通过组和用户权限返回用户拥有的一组权限字符串。

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

has_perm(perm, obj=None)

如果用户具有指定权限,则返回True,其中perm的格式为"<app label>.<permission codename>"(详见permissions)。 如果用户处于非活动状态,此方法将始终返回False

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

has_perms(perm_list, obj=None)

如果用户具有每个指定的权限,并且每个perm格式为"<app label>.<permission codename>",则返回True 如果用户处于非活动状态,此方法将始终返回False

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

has_module_perms(package_name)

如果用户在给定包(Django应用标签)中有任何权限,则返回True 如果用户处于非活动状态,此方法将始终返回False

PermissionsMixin and ModelBackend

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

Custom users and proxy models

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

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

A full example

以下是遵从管理员要求的自定义用户应用的示例。 此用户模型使用电子邮件地址作为用户名,并具有必需的出生日期;它不提供许可检查,除了用户帐户上的简单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):
        """
        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 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 forms.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中的AUTH_USER_MODEL设置将自定义模型指定为项目的默认用户模型:

AUTH_USER_MODEL = 'customauth.MyUser'