在Django中定制身份验证
在Django中定制身份验证
Django附带的认证对于大多数常见情况来说已经足够了,但您可能需要通过开箱即用的默认设置才能满足需求。 要为您的项目定制身份验证,需要了解提供的系统的哪些点可扩展或可替换。
身份验证后端为用户模型存储的用户名和密码需要针对与Django默认不同的服务进行身份验证时提供了一个可扩展的系统。 您可以给您的模型定制可以通过Django的授权系统进行检查的权限。 您可以扩展默认的用户模型,或者替换完全自定义的模型。
其他验证来源
您可能有时需要挂接到另一个身份验证来源 - 也就是另一个用户名和密码来源或身份验证方法。
例如,您的公司可能已经有一个LDAP设置,为每个员工存储用户名和密码。如果用户在LDAP和基于Django的应用程序中有单独的帐户,那么对网络管理员和用户本身来说都是一件麻烦事。
所以,为了处理这样的情况,Django认证系统可以让你插入其他认证源。您可以重写Django的默认基于数据库的方案,或者可以与其他系统一起使用默认系统。
指定认证后端
在幕后,Django维护一个验证后端列表,它检查身份验证。当有人调用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(** credentials),以及一组可选的权限相关授权方法。 get_user方法需要一个user_id - 可以是一个用户名,数据库ID或其他,但必须是你的用户对象的主键 - 并返回一个用户对象。 身份验证方法将凭据作为关键字参数。 大多数情况下,它看起来像这样:
class MyBackend(object):
def authenticate(self, username=None, password=None):
# Check the username/password and return a User.
...
但它也可以验证令牌,如下所示:
class MyBackend(object):
def authenticate(self, token=None):
# Check the token and return a User.
...
无论哪种方式,身份验证都应该检查它获取的凭据,并且如果证书有效,它应该返回与这些凭证相匹配的用户对象。 如果它们无效,则应返回无。
Django管理系统与本章开头描述的Django用户对象紧密耦合。
现在,处理这个问题的最好方法是为每个存在于后端的用户创建一个Django User对象(例如,在您的LDAP目录,外部SQL数据库等中)。您可以编写脚本来执行此操作 或者您的身份验证方法可以在用户首次登录时执行此操作。
以下是一个示例后端,它会根据settings.py文件中定义的用户名和密码变量进行身份验证,并在用户首次进行身份验证时创建一个Django用户对象:
from django.conf import settings
from django.contrib.auth.models import User, check_password
class SettingsBackend(object):
"""
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 = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
"""
def authenticate(self, 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. Note that we can set password
# to anything, because it won't be checked; the password
# from settings.py will.
user = User(username=username, password='password')
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_group_permissions(),get_all_permissions(),has_perm()和has_module_perms())委托给实现这些函数的任何验证后端。 赋予用户的权限将是所有后端返回的所有权限的超集。 也就是说,Django向任何一个后端授予的用户授予权限。
如果后端在has_perm()或has_module_perms()中引发PermissionDenied异常,授权将立即失败,Django将不检查后面的后端。 上面的简单后端可以简单地为管理员实现权限:
class SettingsBackend(object):
...
def has_perm(self, user_obj, perm, obj=None):
if user_obj.username == settings.ADMIN_LOGIN:
return True
else:
return False
这给上面例子中授予访问权限的用户提供了完全的权限。 请注意,除了给予相关用户功能的相同参数之外,后端授权功能都将用户对象(可能是匿名用户)作为参数。
完整的授权实现可以在django / contrib / auth / backends.py中的ModelBackend类中找到,它是默认的后端,它大部分时间都会查询auth_permission表。 如果您希望仅为后端API的一部分提供自定义行为,则可以利用Python继承和子类ModelBackend,而不是在自定义后端中实现完整的API。
匿名用户授权
匿名用户是未经过身份验证的用户,即他们未提供有效的身份验证详细信息。 但是,这并不一定意味着他们无权做任何事情。 在最基本的层面上,大多数网站授权匿名用户浏览大部分网站,并且许多网站允许匿名发布评论等。
Django的权限框架没有地方为匿名用户存储权限。 但是,传递给身份验证后端的用户对象可能是django.contrib.auth.models.AnonymousUser对象,允许后端为匿名用户指定自定义授权行为。
这对于可重用应用程序的作者特别有用,他们可以将授权的所有问题委托给auth后端,而不需要设置,例如控制匿名访问。
对非活跃用户授权
非活跃用户是经过身份验证的用户,但其属性is_active设置为False。 但是,这并不意味着他们无权做任何事情。 例如,他们被允许激活他们的帐户。
对权限系统中的匿名用户的支持允许匿名用户有权执行某些操作,而不活动的经过身份验证的用户则不能这样做。 不要忘记在你自己的后端权限方法中测试用户的is_active属性。
处理对象权限
Django的权限框架为对象权限奠定了基础,但核心中没有实现它。 这意味着检查对象权限将始终返回False或一个空列表(取决于执行的检查)。 身份验证后端将为每个对象相关的授权方法接收关键字参数obj和user_obj,并可以根据需要返回对象级别权限。
自定义权限
要为给定的模型对象创建自定义权限,请使用权限模型元属性。 本示例任务模型创建三个自定义权限,即用户可以使用或不能使用Task实例执行的操作,具体针对您的应用程序:
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迁移时创建这些额外的权限。 当用户试图访问应用程序提供的功能(查看任务,更改任务状态,关闭任务)时,您的代码负责检查这些权限的值。继续上面的示例,以下示例将检查用户 可能会查看任务:
user.has_perm('app.view_task')
扩展现有的用户模型
有两种方法可以扩展默认用户模型,而不用替换自己的模型。 如果您需要的更改是纯粹的行为,并且不需要对存储在数据库中的内容进行任何更改,则可以基于用户创建代理模型。 这允许代理模型提供的任何功能,包括默认排序,自定义管理器或自定义模型方法。
如果您希望存储与用户相关的信息,则可以使用包含字段的模型的一对一关系以获取更多信息。 这种一对一模式通常称为配置文件模型,因为它可能存储有关站点用户的非auth相关信息。 例如,您可以创建一个Employee模型:
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User)
department = models.CharField(max_length=100)
假设现有员工Fred Smith拥有User和Employee模型,则可以使用Django的标准相关模型约定访问相关信息:
>>> u = User.objects.get(username='fsmith')
>>> freds_department = u.employee.department
要将配置文件模型的字段添加到admin的用户页面,请在应用程序的admin.py中定义InlineModelAdmin(对于本示例,我们将使用StackedInline),并将其添加到UserAdmin类,该类用User类注册:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
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(UserAdmin):
inlines = (EmployeeInline, )
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
这些配置文件模型在任何方面都不是特别的 - 它们只是恰好与用户模型具有一对一链接的Django模型。因此,创建用户时不会自动创建,但可以根据需要使用django.db.models.signals.post_save创建或更新相关模型。
请注意,使用相关模型会产生额外的查询或连接以检索相关数据,根据您的需要,替换用户模型并添加相关字段可能是更好的选择。但是,项目应用程序中默认用户模型的现有链接可能会导致额外的数据库负载。
代替自定义用户模型
某些类型的项目可能具有身份验证要求,因此Django的内置User模型并不总是适合的。例如,在一些网站上,使用电子邮件地址作为您的身份标记而不是用户名更有意义。 Django允许您通过为引用自定义模型的AUTH_USER_MODEL设置提供值来覆盖默认的用户模型:
`AUTH_USER_MODEL = 'books.MyUser'`
此虚线对描述了Django应用程序的名称(它必须位于INSTALLED_APPS中),以及您希望用作用户模型的Django模型的名称。
改变AUTH_USER_MODEL对你的Django项目有很大的影响,特别是你的数据库结构。 例如,如果在运行迁移后更改AUTH_USER_MODEL,则必须手动更新数据库,因为它会影响许多数据库表关系的构建。 除非有充分理由这样做,否则不应更改AUTH_USER_MODEL。
尽管有上述警告,但Django完全支持自定义用户模型,但完整的解释超出了本书的范围。 Django项目网站上提供了一个完全符合管理员要求的自定义用户应用程序示例,以及有关自定义用户模型的全面文档。