使用 Django 的验证系统

本文档介绍了 Django 验证系统在默认配置下的使用方法。默认配置满足最常见的项目需求,可以处理相当多的任务,还有一个安全的密码和权限实现。对于验证需求与默认配置不同的项目,Django 支持对身份验证进行扩展和定制。

Django 验证同时提供身份验证和授权,通常称为身份验证系统,因为这些功能在某种程度上是耦合的。

User 对象

用户对象是认证系统的核心。它通常代表了与你的站点交互的人员,并用于允许诸如限制访问、注册用户配置文件、将内容与创建者关联等功能。Django 的认证框架中用户只有一个类,例如 “超级管理员”或“普通管理员”只是具有特殊属性集的用户对象,而不是用户对象的不同类。

默认用户的主要属性是:

请参阅完整的API文档 full API documentation 以获得完整的参考,下面的文档主要以任务为导向。

创建用户

创建用户最直接的方法是使用包含 create_user() 的函数:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

# At this point, user is a User object that has already been saved
# to the database. You can continue to change its attributes
# if you want to change other fields.
>>> user.last_name = 'Lennon'
>>> user.save()

如果你已经安装了 Django admin 管理后台,你也可以在 admin 管理后台交互式地创建用户:ref:create users interactively <auth-admin>

创建超级用户

通过命令行 createsuperuser 创建超级管理员:

$ python manage.py createsuperuser --username=joe --email=joe@example.com

你将会被提示输入密码,完成之后,超级管理员就被创建成功了。如果你没有填写参数 --username <createsuperuser --username> ` or :option:--email <createsuperuser --email>` ,也将会被提示输入这些值。

更改密码

Django 不会在用户模型里保存原始(明文)密码,而只会存储哈希值(请参阅文档 如何管理密码 documentation of how passwords are managed ) 。因此,请不要试图直接操作用户的密码,这就是创建用户需要辅助函数的原因。

更改一个用户的密码,你有几个选择:

manage.py changepassword *username* 提供了在命令行修改用户密码的方法。它会提示你输入两次新密码,如果操作成功,新密码就立刻生效。如果你没有提供参数 username ,那么将会尝试修改当前系统用户的密码。

你也可以在代码里修改密码,使用 set_password():

>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username='john')
>>> u.set_password('new password')
>>> u.save()

如果你已经按照了 Django admin 管理后台,你也可以在管理后台页面修改密码(请参阅 :ref:`authentication system's admin pages ` )。

Django 还提供了允许用户自行修改密码的 :ref:`views ` 和 :ref:`forms ` 。

修改密码将会注销用户的所有会话。查看详情请参阅 密码更改时会话失效

验证用户

authenticate(request=None, **credentials)

使用 authenticate() 来验证用户。它使用 usernamepassword 作为参数来验证,对每个身份验证后端( authentication backend ` )进行检查。如果后端验证有效,则返回一个 :class:`~django.contrib.auth.models.User 对象。如果后端引发 PermissionDenied 错误,将返回 None。举例:

from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    # A backend authenticated the credentials
else:
    # No backend authenticated the credentials

request 是可选的 HttpRequest ,它在身份验证后端上的 authenticate() 方法来传递。

注解

这个一个很底层的验证方法。比如,可以通过 RemoteUserMiddleware 来验证。除非你在编写自己的身份验证系统,否则你可能不会用到它。如果你正在寻找用户登录的方法,请参阅 LoginView

权限和认证

Django 内置了一个权限系统。它提供了为指定的用户和用户组分配权限的方法。

它在 Django 管理后台界面里使用,但你也可以在自己的代码中使用它。

Django 的 admin 页面使用了如下权限:

  • 访问查看的对象仅限于具有该类型对象的“查看”或“更改”权限的用户。
  • 访问“添加”表单和添加对象仅限于具有该类型对象的“添加”权限的用户。
  • 访问修改列表、查看“修改”表单和修改对象仅限于对该类型对象的“修改”权限的用户。
  • 访问删除对象仅限于对该类型对象的“删除”权限的用户。

不仅可以为每个对象类型设置权限,还可以为每个指定对象实例设置权限。通过使用 ModelAdmin 类提供的 has_view_permission(), has_add_permission(), has_change_permission()has_delete_permission() 方法,可以为同一类型的不同实例定制权限。

User 对象有两个多对多字段:groupsuser_permissionsUser 对象可以像访问其他 :doc:`Django model `: 一样访问他们的相关对象。

myuser.groups.set([group_list])
myuser.groups.add(group, group, ...)
myuser.groups.remove(group, group, ...)
myuser.groups.clear()
myuser.user_permissions.set([permission_list])
myuser.user_permissions.add(permission, permission, ...)
myuser.user_permissions.remove(permission, permission, ...)
myuser.user_permissions.clear()

默认权限

INSTALLED_APPS 设置了 django.contrib.auth 时,它将确保你的每个 Django 模型被创建时有四个默认权限:添加、修改、删除和查看。

运行 manage.py migrate 时将创建这些权限。当你添加 django.contrib.authINSTALLED_APPS 后第一次运行 迁移 ,将会为所有只去已经安装过的模型以及现在正在安装的模型创建这些默认的权限。之后,每次你运行 manage.py migrate 都会为新模型创建默认权限 (创建权限的函数连接 post_migrate 信号)。

假设你有一个名为 foo 应用程序和一个名为 Bar 的模型,要测试基础权限,你应该使用:

  • 添加:user.has_perm('foo.add_bar')
  • 修改:user.has_perm('foo.change_bar')
  • 删除:user.has_perm('foo.delete_bar')
  • 查看:user.has_perm('foo.view_bar')

权限模型很少会被直接访问。

django.contrib.auth.models.Group 模型是对用户进行分类的通用方法,因此您可以将权限或其他标签应用于这些用户。用户可以属于任意数量的组。

组里的用户会自动拥有该组的权限。举例,如果 Site editors 组有修改网站首页的权限,那么该组的任何成员都有这个权限。

除权限外,组是一个方便的途径,可以给用户分类,为其提供一些标签或扩展功能。例如,你可以创建一个组 'Special users',并在编写的代码里让该组成员访问网站仅限会员部分的内容,或者对该组成员发送仅限会员查看的电子邮件。

以编程方式创建权限

虽然可以在模型的 Meta 类中定义 custom permissions ,你也可以直接创建权限。例如,你可以为 BlogPost 模型创建 can_publish 权限。

from myapp.models import BlogPost
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(
    codename='can_publish',
    name='Can Publish Posts',
    content_type=content_type,
)

然后,可以通过 user_permissions 属性将权限分配给 User ,或通过 permissions 属性分配给 Group  。

代理模型需要自己的内容类型

如果你想创建 permissions for a proxy model ,传递 for_concrete_model=FalseContentTypeManager.get_for_model() 来获取合适的 ContentType

content_type = ContentType.objects.get_for_model(BlogPostProxy, for_concrete_model=False)

权限缓存

在第一次需要获取用户对象的权限检查时, ModelBackend 才会缓存它们的权限。对于请求-响应周期来说,这通常是很好的,因为权限通常不会在添加的时候立刻检查(例如,在 admin 中)。如果你打算在测试或视图中添加权限,并随后检查他们,最简单的解决方案就是从数据库中重新获取用户。例如:

from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404

from myapp.models import BlogPost

def user_gains_perms(request, user_id):
    user = get_object_or_404(User, pk=user_id)
    # any permission check will cache the current set of permissions
    user.has_perm('myapp.change_blogpost')

    content_type = ContentType.objects.get_for_model(BlogPost)
    permission = Permission.objects.get(
        codename='change_blogpost',
        content_type=content_type,
    )
    user.user_permissions.add(permission)

    # Checking the cached permission set
    user.has_perm('myapp.change_blogpost')  # False

    # Request new instance of User
    # Be aware that user.refresh_from_db() won't clear the cache.
    user = get_object_or_404(User, pk=user_id)

    # Permission cache is repopulated from the database
    user.has_perm('myapp.change_blogpost')  # True

    ...

代理模型

代理模型的工作方式和具体模型完全相同。代理模型使用自己的内容类型创建权限。代理模型不会继承其子类的具体模型权限。

class Person(models.Model):
    class Meta:
        permissions = [('can_eat_pizzas', 'Can eat pizzas')]

class Student(Person):
    class Meta:
        proxy = True
        permissions = [('can_deliver_pizzas', 'Can deliver pizzas')]

>>> # Fetch the content type for the proxy model.
>>> content_type = ContentType.objects.get_for_model(Student, for_concrete_model=False)
>>> student_permissions = Permission.objects.filter(content_type=content_type)
>>> [p.codename for p in student_permissions]
['add_student', 'change_student', 'delete_student', 'view_student',
'can_deliver_pizzas']
>>> for permission in student_permissions:
...     user.user_permissions.add(permission)
>>> user.has_perm('app.add_person')
False
>>> user.has_perm('app.can_eat_pizzas')
False
>>> user.has_perms(('app.add_student', 'app.can_deliver_pizzas'))
True

Web 请求的认证

Django 使用 sessions 和中间件将身份验证系统挂接到请求对象中。

它们在每次请求中都会提供 request.user 属性。如果当前没有用户登录,这个属性将会被设置为 AnonymousUser ,否则将会被设置为 User 实例。

你可以使用 is_authenticated 区分两者,例如:

if request.user.is_authenticated:
    # Do something for authenticated users.
    ...
else:
    # Do something for anonymous users.
    ...

用户如何登陆

如果有一个已验证的用户想附加到当前会话(session)中,将通过 login()  函数完成。

login(request, user, backend=None)

要在视图中让用户登录,使用 login() 。它需要 HttpRequest 对象和 User 对象。通过 Django 的 session 框架, login() 会在 session 中保存用户的ID。

注意,在匿名会话期间设置的任何数据都会在用户登录后保留在会话中。

这个例子展示了如何使用 authenticate() 和 login(): :

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

选择验证后端

当用户登录时,用户 ID 和用于身份验证的后端会被保存在用户会话中。允许相同的 authentication backend 在未来的请求中获取用户详情。选择要在会话中保存的验证后端如下:

  1. 使用提供了的可选 backend 参数值。
  2. 使用 user.backend  的值。允许配对 authenticate()  和 login() :当返回用户对象时 authenticate() 设置 user.backend 属性。
  3. 使用 AUTHENTICATION_BACKENDS 存在的 backend 。
  4. 否则,抛出一个异常。

在1和2中,backend 参数和 user.backend 属性应该是完整的导入路径(像 AUTHENTICATION_BACKENDS 里的路径一样),而不是真实的后端类。

用户如何登出

logout(request)

如果已经通过 django.contrib.auth.login() 登录的用户想退出登录,可以在视图中使用 django.contrib.auth.logout() 。需要传入 HttpRequest 对象,并且该函数不会返回值。例如:

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

注意,如果用户未登录,logout() 不会报错。

调用 logout() 后,当前请求的会话数据会被全部清除。这是为了防止其他使用同一个浏览器的用户访问前一名用户的会话数据。如果想在登出后立即向用户提供的会话中放入任何内容,请在调用 django.contrib.auth.logout() 之后执行此操作。

限制对未登录用户的访问

原始方式

限制访问页面最原始的办法就是检查 request.user.is_authenticated 并重定向到登录页面。

from django.conf import settings
from django.shortcuts import redirect

def my_view(request):
    if not request.user.is_authenticated:
        return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
    # ...

或者显示一个错误信息:

from django.shortcuts import render

def my_view(request):
    if not request.user.is_authenticated:
        return render(request, 'myapp/login_error.html')
    # ...

login_required 装饰器

login_required(redirect_field_name='next', login_url=None)

作为快捷方式,你可以使用 login_required() 装饰器:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

login_required() 会执行以下操作:

  • 如果用户没有登录,会重定向到 settings.LOGIN_URL ,并传递绝对路径到查询字符串中。例如: /accounts/login/?next=/polls/3/
  • 如果用户已经登录,则正常执行视图。视图里的代码可以假设用户已经登录了。

默认情况下,成功验证时用户跳转的路径保存在名为 "next" 的查询字符串参数中。如果你希望这个参数使用不同名称,请在 login_required() 中传递可选参数 redirect_field_name :

from django.contrib.auth.decorators import login_required

@login_required(redirect_field_name='my_redirect_field')
def my_view(request):
    ...

注意,如果你提供了 redirect_field_name 值,则很可能也需要自定义登录模板,因为存储重定向路径的模板上下文变量使用的是 redirect_field_name 值,而不是 "next" (默认情况下)。

login_required() 也有可选参数 login_url 。例如:

from django.contrib.auth.decorators import login_required

@login_required(login_url='/accounts/login/')
def my_view(request):
    ...

注意,如果你没有指定参数 login_url ,你需要确认 settings.LOGIN_URL  和登录视图是正确关联的。例如,使用默认方式,在 URL 配置文件里添加下面这行:

from django.contrib.auth import views as auth_views

path('accounts/login/', auth_views.LoginView.as_view()),

settings.LOGIN_URL 也接受视图方法名和 named URL patterns 。这样你可以在 URLconf 里自由地重新映射你的登录视图,而不需更新配置文件。

注解

login_required 装饰器不会检查用户的 is_active 标识状态,但默认的 AUTHENTICATION_BACKENDS 会拒绝非正常用户。

参见

如果你打算编写自定义的 Django 管理模块视图(或需要与内置视图使用同样的权限检查),你将会发现 django.contrib.admin.views.decorators.staff_member_required() 装饰器是 login_required() 的一个有用的替代方法。

LoginRequired Mixin

使用基于类的视图时,可以使用 LoginRequiredMixin 实现和 login_required 相同的行为。这个 Mixin 应该在继承列表中最左侧的位置。

class LoginRequiredMixin

如果一个视图使用 Mixin ,那么未经验证用户的所有请求都会被重定向到登录页面或者显示 HTTP 403 Forbidden 错误,这取决于 raise_exception 参数。

你可以设置 AccessMixin 的任何参数来自定义未验证用户的处理:

from django.contrib.auth.mixins import LoginRequiredMixin

class MyView(LoginRequiredMixin, View):
    login_url = '/login/'
    redirect_field_name = 'redirect_to'

注解

login_required 装饰器一样,Mixin 不会检查用户的 is_active 标识状态,但默认的 AUTHENTICATION_BACKENDS 会拒绝非正常用户。

限制对通过测试的登录用户的访问

根据某些权限或者其他测试来限制访问,你基本上可以执行和上一节所述同样的操作。

可以在视图里直接对 request.user 进行测试。举例,这个视图检查用户是否拥有特定域名的邮箱,否则会重定向到登录页:

from django.shortcuts import redirect

def my_view(request):
    if not request.user.email.endswith('@example.com'):
        return redirect('/login/?next=%s' % request.path)
    # ...
user_passes_test(test_func, login_url=None, redirect_field_name='next')

作为快捷方式,你可以方便的调用 user_passes_test 装饰器,当调用返回 False 时会执行重定向。

from django.contrib.auth.decorators import user_passes_test

def email_check(user):
    return user.email.endswith('@example.com')

@user_passes_test(email_check)
def my_view(request):
    ...

user_passes_test() 接受一个必要的参数:一个带有:class:~django.contrib.auth.models.User 对象的调用,如果允许用户访问这个页面,则返回 True 。注意,user_passes_test() 不会自动检查用户是否匿名。

user_passes_test() 可以传递两个可选参数:

login_url
允许你指定用户没有通过测试时跳转的地址。它可能是一个登录页面,如果你没指定,默认是 settings.LOGIN_URL 。
redirect_field_name
login_required() 相同。如果你想把没通过检查的用户重定向到没有 "next page" 的非登录页面时,把它设置为 None ,这样它会在 URL 中移除。

例如:

@user_passes_test(email_check, login_url='/login/')
def my_view(request):
    ...
class UserPassesTestMixin

使用基于类的视图时,可以使用 UserPassesTestMixin 执行此操作。

test_func()

你必须覆盖类方法 test_func() 以提供执行的测试。此外,还可以设置 AccessMixin 的任何参数来自定义处理未授权用户:

from django.contrib.auth.mixins import UserPassesTestMixin

class MyView(UserPassesTestMixin, View):

    def test_func(self):
        return self.request.user.email.endswith('@example.com')
get_test_func()

你也可以覆盖 get_test_func() 方法,以使 mixin 对其检查使用不同名称的函数(而不是 test_func() )。

集成 UserPassesTestMixin

由于实现了 UserPassesTestMixin 方式,不能在继承列表中集成它们。下述方式将不能工作:

class TestMixin1(UserPassesTestMixin):
    def test_func(self):
        return self.request.user.email.endswith('@example.com')

class TestMixin2(UserPassesTestMixin):
    def test_func(self):
        return self.request.user.username.startswith('django')

class MyView(TestMixin1, TestMixin2, View):
    ...

如果 TestMixin1 调用 super() 并把结果考虑在内,TestMixin1 将不能独立运行。

permission_required 装饰器

permission_required(perm, login_url=None, raise_exception=False)

检查用户是否拥有特定的权限是一个相对常见的任务。出于这个原因,Django 提供了一个快捷方式:permission_required() 装饰器:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.add_choice')
def my_view(request):
    ...

就像 has_perm() 方法一样,权限名称采用 "<app label>.<permission codename>" 形式(比如 polls.polls.add_choice 就是 polls 应用程序下的模型的权限)。

装饰器也可以接受可迭代权限,在这种情况下,用户必须拥有所有权限才能访问视图。

注意, permission_required() 也可以接受可选的 login_url 参数:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.add_choice', login_url='/loginpage/')
def my_view(request):
    ...

login_required() 装饰器一样, login_url 默认是:setting:settings.LOGIN_URL <LOGIN_URL>

如果有 raise_exception 参数,那么装饰器将引发 PermissionDenied 错误,提示 the 403 (HTTP Forbidden) view 而不是跳转到登录页面。

如果你想使用 raise_exception 但也想给用户登录的机会,那需要添加 login_required() 装饰器:

from django.contrib.auth.decorators import login_required, permission_required

@login_required
@permission_required('polls.add_choice', raise_exception=True)
def my_view(request):
    ...

LoginViewredirect_authenticated_user=True 并且已登录用户没有所有必须的权限时,这避免了重定向循环。

PermissionRequiredMixin Mixin

class-based views 中应用权限检查,你可以使用 PermissionRequiredMixin

class PermissionRequiredMixin

permission_required 装饰器一样,Mixin 检查用户访问的视图是否拥有全部的权限。你应该使用 permission_required 指定权限(或者可迭代权限):

from django.contrib.auth.mixins import PermissionRequiredMixin

class MyView(PermissionRequiredMixin, View):
    permission_required = 'polls.add_choice'
    # Or multiple of permissions:
    permission_required = ('polls.view_choice', 'polls.change_choice')

你可以设置 AccessMixin 的任意参数来自定义处理没有权限的用户。

你可能同样需要重写这些方法:

get_permission_required()

返回 Mixin 使用的可迭代权限的名称。默认为 permission_required 属性,如果需要可以转化为元组。

has_permission()

返回布尔值,表示当前用户是否拥有权限执行装饰器视图。默认情况下,这将返回调用 has_perms() 的结果,其中包括返回 get_permission_required() 的权限列表。

在基于类的视图中重定向未通过验证的请求

为了简化基于类的视图限制访问的处理方式,AccessMixin 被用来配置当访问被拒绝时的视图行为。

class AccessMixin
login_url

get_login_url() 的缺省返回值。默认是 None ,在这种情况下, get_login_url() 会回退至 settings.LOGIN_URL

permission_denied_message

get_permission_denied_message() 的缺省返回值。默认是空字符串。

redirect_field_name

get_redirect_field_name() 的缺省返回值。默认是 "next"

raise_exception

如果这个属性被设置为 True ,当条件不被满足的时候会引发 PermissionDenied 异常。如果是 False (默认),匿名用户会被重定向至登录页面。

get_login_url()

返回当用户没有通过测试时将被重定向的网址。如果已设置,将返回 login_url ,否则返回 settings.LOGIN_URL

get_permission_denied_message()

raise_exception 为 True 时,这个方法可以控制传递给错误处理程序的错误信息,以便显示给用户。默认返回 permission_denied_message 属性。

get_redirect_field_name()

返回查询参数名,包含用户登录成功后重定向的 URL 。如果这个值设置为 None ,将不会添加查询参数。默认返回 redirect_field_name 属性。

handle_no_permission()

根据 raise_exception 的值,这个方法将会引发 PermissionDenied 异常或重定向用户至 login_url ,如果已设置,则可选地包含 redirect_field_name

密码更改时会话失效

如果 AUTH_USER_MODEL 继承自 AbstractBaseUser 或实现了自己的 get_session_auth_hash() 方法,已验证的会话将包含这个函数返回的哈希值。在 AbstractBaseUser 的情况下,这是密码字段的 HMAC 。Django 验证每个请求的会话中的哈希是否与请求期间计算的哈希相匹配。这允许用户修改密码来注销所有会话。

Django 包含默认的密码修改视图,PasswordChangeView 和 user_change_password 视图在 django.contrib.auth admin 中,将使用新密码的哈希更新会话,因此用户修改密码后不会被注销。如果你有自定义的密码修改视图,并期望有同样的行为,可以使用 update_session_auth_hash() 函数。

update_session_auth_hash(request, user)

这个函数接受当前请求和从新会话哈希派生时更新的用户对象,并会更新哈希值。它也会替换哈希值因此被盗用的会话cookie会无效。

用法示例:

from django.contrib.auth import update_session_auth_hash

def password_change(request):
    if request.method == 'POST':
        form = PasswordChangeForm(user=request.user, data=request.POST)
        if form.is_valid():
            form.save()
            update_session_auth_hash(request, form.user)
    else:
        ...

注解

因为 get_session_auth_hash() 是基于 SECRET_KEY 的,因此更新站点以使用密钥将会导致所有存在的会话失效。

验证视图

Django 提供许多可以用来处理登录、注销和密码管理的视图。这些利用了 stock auth forms ,但你也可以使用自己的表单。

Django 没有为验证视图提供默认模板。你可以为你打算使用的视图创建自己的模板。每个视图都记录了模板上下文,详情查看 所有的验证视图

使用视图

在项目中可以使用不同方法来实现这些视图。最简单的方法就是在 URLconf 中包含 django.contrib.auth.urls 提供的 URLconf 。举例:

urlpatterns = [
    path('accounts/', include('django.contrib.auth.urls')),
]

这将包含以下 URL 模式:

accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']

视图提供 URL 名称以便参考。有关使用命名 URL 模式的使用详情参考 the URL documentation

如果你想更好的控制 URL ,你可以在你的 URLconf 中引用特定的视图:

from django.contrib.auth import views as auth_views

urlpatterns = [
    path('change-password/', auth_views.PasswordChangeView.as_view()),
]

这个视图具有更改视图行为的可选参数。例如,如果你想改变视图使用的模板名称,你可以提供 template_name 参数。在 URLconf 中提供关键字参数,这些参数将被传递给视图。举例:

urlpatterns = [
    path(
        'change-password/',
        auth_views.PasswordChangeView.as_view(template_name='change-password.html'),
    ),
]

所有视图都是基于类的,这就允许你通过子类去方便地自定义它们。

所有的验证视图

这是一个 django.contrib.auth 提供的所有视图列表。有关实现细节可参考 使用视图

class LoginView

URL name: login

有关使用命名 URL 模式的细节可参考 the URL documentation

属性:

  • template_name :用户登录的视图所使用的模板名称。默认指向 registration/login.html

  • redirect_field_nameGET 字段包含的登录后跳转 URL 的参数名称。默认是 next

  • authentication_form :用于验证的调用(通常是一个表单类)。默认是 AuthenticationForm

  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。

  • redirect_authenticated_user :布尔值,用来控制已验证的用户访问登录页面是否被重定向,就像他们刚刚成功登录一样。默认是 False

    警告

    如果你启用了 redirect_authenticated_user ,其他网站通过重定向请求你的网站的图片文件的方式来确定他们的访客是否是你网站的已验证用户。为了避免这个 "social media fingerprinting" 信息泄露,请将所有图片和 favicon 都托管在单独的域名中。

    启用 redirect_authenticated_user 也会在使用 permission_required() 装饰器的时候,导致重定向循环,除非 raise_exception 参数被使用。

  • success_url_allowed_hosts :除了 request.get_host() 之外的主机集合,登录后能安全地重定向。默认是空集。

LoginView 能做什么:

  • 如果通过 GET 调用,它将显示 POST 到同一 URL 地址的登录表单。稍后会详细介绍。
  • 如果用户提交的数据通过 POST 调用,那它将试着让用户登录。如果登录成功,那么视图将重定向到 next 指定的 URL 。如果没有提供 next ,它将重定向到 settings.LOGIN_REDIRECT_URL (默认 /accounts/profile/ )。如果登录没有成功,它将重新显示登录表单。

你需要提供登录模板,默认调用 registration/login.html 。这个模板传递四个模板上下文变量:

  • form :一个代表 AuthenticationFormForm 对象。
  • next :登录成功后跳转的网址。这可能包含查询字段。
  • site :根据 SITE_ID 设置的当前站点。如果你还没有安装站点框架,会将其设置为 RequestSite 实例,该实例从当前 HttpRequest 中派生出站点名和域名。
  • site_namesite.name 的别名。如果你还没有按照站点框架,它将设置为 request.META['SERVER_NAME'] 的值。更多关于站点信息,请参考 “站点”框架

如果你不愿意调用模板 registration/login.html ,你可以通过附加参数的形式传递 template_name 参数给你的 URLconf 中的 as_view 方法。比如,这行 URLconf 将使用 myapp/login.html 代替:

path('accounts/login/', auth_views.LoginView.as_view(template_name='myapp/login.html')),

你也可以使用 redirect_field_name 指定 GET 字段的名称,这个字段包含登陆后跳转的 URL 地址。默认情况下,这个字段为 next

下面是一个简单的 registration/login.html 模板。它假设你有 base.html 模板,并且已经定义了 content 块:

{% extends "base.html" %}

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

{% if next %}
    {% if user.is_authenticated %}
    <p>Your account doesn't have access to this page. To proceed,
    please login with an account that has access.</p>
    {% else %}
    <p>Please login to see this page.</p>
    {% endif %}
{% endif %}

<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
</tr>
<tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
</tr>
</table>

<input type="submit" value="login">
<input type="hidden" name="next" value="{{ next }}">
</form>

{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>

{% endblock %}

如果你具有自定义身份验证(查看 Customizing Authentication ),可以通过设置 authentication_form 属性来使用自定义的验证码模板。此表单必须在它的 __init__() 方法中接受 request 关键字参数,并且提供一个返回已验证用户对象的 get_user() 方法(这个方法只会在表单成功验证后调用)。

class LogoutView

注销用户

URL name: logout

属性:

  • next_page :注销后将要跳转的 URL 。默认是 settings.LOGOUT_REDIRECT_URL
  • template_name :用户注销后显示的模板地址。默认是 registration/logged_out.html
  • redirect_field_name :注销后包含重定向 URL 的 GET 字段名称。默认是 next 。如果传递给定的 GET 参数,将会覆盖 next_page URL。
  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。
  • success_url_allowed_hosts :除了 request.get_host() 之外的主机集合,在注销后可以安全地重定向。默认是空集。

Template context:

  • title :字符串 "已注销",已本地化。
  • site :根据 SITE_ID 设置的当前站点。如果你还没有安装站点框架,会将其设置为 RequestSite 实例,该实例从当前 HttpRequest 中派生出站点名和域名。
  • site_namesite.name 的别名。如果你还没有按照站点框架,它将设置为 request.META['SERVER_NAME'] 的值。更多关于站点信息,请参考 “站点”框架
logout_then_login(request, login_url=None)

用户注销,然后重定向到登录页面。

URL name: 没有提供默认的 URL

可选参数:

  • login_url :要重定向到登录页面的 URL 。如果没有提供,默认是 settings.LOGIN_URL
class PasswordChangeView

URL name: password_change

允许用户修改密码。

属性:

  • template_name :用于显示密码修改表单的模板全称。如果没提供则默认 registration/password_change_form.html
  • success_url :密码修改成功后跳转的 URL 。默认是 'password_change_done'
  • form_class :一个自定义修改密码的表单,此表单必须接收 user 关键字参数。表单负责实际修改用户密码。默认是 PasswordChangeForm
  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。

Template context:

  • form :密码修改表单(参考上面的 form_class
class PasswordChangeDoneView

URL name: password_change_done

用户修改密码之后显示的页面。

属性:

  • template_name :所使用模板的全称。如果没有提供,则默认 registration/password_change_done.html
  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。
class PasswordResetView

URL name: password_reset

允许用户通过生成的一次性链接来重置密码,并把一次性链接发到用户注册邮箱中。

如果邮箱地址并不存在,视图不会发送邮件,但用户也不会受到任何错误信息。这是为了防止信息泄露给潜在的攻击者。如果你想提供错误信息,你可以成为继承 PasswordResetForm ,并使用 form_class 属性。

注解

注意,发送邮件需要花费额外时间,因此,由于对现有电子邮件地址的重置请求的持续时间与对不存在的电子邮件地址的重置请求的持续时间不同,你可能容易受到电子邮件地址枚举定时攻击。为了降低开销,你可以使用第三方插件来允许异步发送邮件,例如 django-mailer

不可使用密码的用户 (查看 set_unusable_password() )不能请求重置密码,以防止在使用类似 LDAP 等外部验证源时滥用。注意,它们将无法收到错误信息,因为这将暴露用户账号是否存在,并且也不会发送邮件。

属性:

  • template_name :用来显示密码重置表单的模板全称。如果没提供,则默认 registration/password_reset_form.html 。
  • form_class :用于获取用户的重置密码的电子邮件的表单。默认是 PasswordResetForm
  • email_template_name :用来生成重置密码链接的电子邮件模板。如果没有提供,默认是 registration/password_reset_email.html 。
  • subject_template_name :重置密码电子邮件主题的模板全称。如果没有提供,则是 registration/password_reset_subject.txt 。
  • token_generator :检测一次性链接的类实例。默认是 default_token_generator ,这是 django.contrib.auth.tokens.PasswordResetTokenGenerator 的一个实例。
  • success_url: 密码重置请求成功后跳转的 URL 。默认是 'password_reset_done'
  • from_email :合法邮件地址。默认情况下,Django 使用 DEFAULT_FROM_EMAIL
  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。
  • html_email_template_name :用来生成带有密码重置链接的:mimetype:`text/html`多用途电子邮件。默认情况下,HTML 单子邮件不会被发送。
  • extra_email_context: 在邮件模板提供上下文数据字典。它被用来覆盖下面列出的默认模板上下文值,比如 domain

Template context:

  • form :重置用户密码的表单(可参考上面的 form_class )。

电子邮件模板上下文:

  • emailuser.email 的别名。
  • user :根据电子邮件表单字段指定的当前用户。只有活动用户才可以重置密码( User.is_active True )。
  • site_namesite.name 的别名。如果你还没有按照站点框架,它将设置为 request.META['SERVER_NAME'] 的值。更多关于站点信息,请参考 “站点”框架
  • domainsite.domain 的别名。如果你还没有安装网站框架,它会被设置为 request.get_host() 的值。
  • protocol :http 或 https
  • uid :使用 Base64 编码过的用户主键。
  • token :检测重置密码链接是否有效的 Token 。

registration/password_reset_email.html 例子(电子邮件内容模板)

Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}

相同的模板上下文用于主题模板。主题必须是单行纯文本字符串。

class PasswordResetDoneView

URL name: password_reset_done

发送重置密码邮件后显示的页面。如果 PasswordResetView 没有明确设置 success_url ,那么默认将该视图调用。

注解

如果提供的电子邮件地址在系统内并不存在,那么用户可能是非活动用户,或者密码不可被更改。用户仍然会重定向到这个视图,但不会发送邮件。

属性:

  • template_name :被使用模板的全称。如果没有提供,则默认是 registration/password_reset_done.html
  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。
class PasswordResetConfirmView

URL name: password_reset_confirm

提供输入新密码的表单。

URL 的关键参数

  • uidb64 :被Base64编码过的用户 id。
  • token :检查密码是否有效的 Token。

属性:

  • template_name :用于显示确认密码视图的模板全称。默认值是 registration/password_reset_confirm.html
  • token_generator :检查密码的类实例。默认是 default_token_generator,它是 django.contrib.auth.tokens.PasswordResetTokenGenerator 的实例。
  • post_reset_login :布尔值,用来标示用户成功重置密码后是否会被自动验证。默认是 False
  • post_reset_login_backend :验证后端的路径,用于当 post_reset_loginTrue 时验证用户使用。当拥有多个 AUTHENTICATION_BACKENDS 设置时才会用到。默认为 None
  • form_class :用来设置密码的表单。默认是 SetPasswordForm
  • success_url :重置密码完成后跳转的 URL 。默认 'password_reset_complete'
  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。
  • reset_url_token: Token 参数为密码重置链接的组成部分。默认 'set-password'

Template context:

  • form :用来设置用户新密码的表单(查看上面的 form_class )。
  • validlink :布尔值,如果链接(uidb64token 的组合)有效且合法,则返回 True 。
class PasswordResetCompleteView

URL name: password_reset_complete

当密码已经被修改成功后通知用户的视图。

属性:

  • template_name :显示视图的模板全称。默认 registration/password_reset_complete.html
  • extra_context :上下文数据字典,通过模板添加到默认上下文数据中。

辅助函数

redirect_to_login(next, login_url=None, redirect_field_name='next')

重定向到登录页面,登陆成功后跳转到其他 URL 。

必要参数

  • next :成功登陆后跳转的 URL。

可选参数:

  • login_url :要重定向到登录页面的 URL 。如果没有提供,默认是 settings.LOGIN_URL
  • redirect_field_name :注销后跳转的 URL 所包含的 GET 字段名称。如果已传递给定的 GET 参数,则覆盖 next

内置表单

如果你不想使用内置视图,但希望不必为此功能编写表单,验证系统提供很多在 django.contrib.auth.forms 中的内置表单。

class AdminPasswordChangeForm

在管理界面修改用户密码所使用的表单。

user 作为第一个参数。

class AuthenticationForm

用户登录的表单。

request 作为第一个参数,该参数存储在表单实例上,供子类使用。

confirm_login_allowed(user)

默认情况下,AuthenticationForm 拒绝 is_active 标识为 False 的用户。你可以通过自定义策略来覆盖这个行为来决定哪个用户可以登录。通过``AuthenticationForm`` 子类的自定义表单来执行这个操作,并覆盖 confirm_login_allowed() 方法。如果给定的用户无法登录,这个方法应该引发 ValidationError 错误。

举例,允许所有用户登录而不管 "active" 状态:

from django.contrib.auth.forms import AuthenticationForm

class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
    def confirm_login_allowed(self, user):
        pass

(在这个例子中,你将需要使用一个允许非活动用户的验证后端,比如 AllowAllUsersModelBackend 。)

或者只允许一些活动用户登录:

class PickyAuthenticationForm(AuthenticationForm):
    def confirm_login_allowed(self, user):
        if not user.is_active:
            raise ValidationError(
                _("This account is inactive."),
                code='inactive',
            )
        if user.username.startswith('b'):
            raise ValidationError(
                _("Sorry, accounts starting with 'b' aren't welcome here."),
                code='no_b_users',
            )
class PasswordChangeForm

允许用户修改密码的表单:

class PasswordResetForm

生成和邮件发送一次性重置密码链接的表单。

send_mail(subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None)

使用参数发送 EmailMultiAlternatives。可以重写自定义邮件发送给用户的方式。

参数:
  • subject_template_name -- 主题模板
  • email_template_name -- 邮件主体模板
  • context -- 上下文传递至 subject_template, email_template, 和 html_email_template (如果它非空)。
  • from_email -- 发送方的邮箱地址。
  • to_email -- 接收方的邮箱地址。
  • html_email_template_name -- HTML 正文模板;默认是 None,在这种情况下发送纯文本邮件。

默认情况下,save() 使用 PasswordResetView 传递给其电子邮件上下文的相同变量填充上下文。

class SetPasswordForm

让用户不输入旧密码就能改变它们密码的表单。

class UserChangeForm

在管理界面修改用户信息和权限的表单。

class UserCreationForm

建立新用户的 ModelForm

它有三个字段: username (来自用户模型),password1 ,和 password2 。它检查 password1password2 是否匹配,使用 validate_password() 验证密码,并且使用 set_password() 来设置用户密码。

模板内验证数据

当你使用 RequestContext 时,当前已登录用户和他们的权限在 :doc:`template context ` 中是可用的。

技术细节

从技术上讲,如果你使用 RequestContext ,这些变量只在模板上下文中可用,并且已启用 'django.contrib.auth.context_processors.auth' 处理机。它默认产生配置文件。了解更多,可以查看 RequestContext docs

用户

当渲染模板 RequestContext ,当前登录用户(User 实例或 AnonymousUser 实例)被保存在模板变量 {{ user }} 中:

{% if user.is_authenticated %}
    <p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
    <p>Welcome, new user. Please log in.</p>
{% endif %}

如果没有使用 RequestContext ,那么这个模板上下文变量将不可用。

权限

当前登录用户的权限保存在模板变量 {{ perms }} 中。这是 django.contrib.auth.context_processors.PermWrapper 的一个实例,它是一个对模板友好的权限代理。

{{ perms }} 的单属性查找作为布尔值为 User.has_module_perms() 的代理。比如,检测登录用户是否拥有一些权限:

{% if perms.foo %}

将两层属性查找作为布尔值是 User.has_perm() 的代理。比如,检测登录用户是否拥有 foo.add_vote 权限:

{% if perms.foo.add_vote %}

以下是一个在模板中检查权限的更完整的示例:

{% if perms.foo %}
    <p>You have permission to do something in the foo app.</p>
    {% if perms.foo.add_vote %}
        <p>You can vote!</p>
    {% endif %}
    {% if perms.foo.add_driving %}
        <p>You can drive!</p>
    {% endif %}
{% else %}
    <p>You don't have permission to do anything in the foo app.</p>
{% endif %}

也可以通过 {% if in %} 语句来查找权限。比如:

{% if 'foo' in perms %}
    {% if 'foo.add_vote' in perms %}
        <p>In lookup works, too.</p>
    {% endif %}
{% endif %}

在管理界面中管理用户

当安装了 django.contrib.admindjango.contrib.auth,管理后台提供了方便的方法来查看和管理用户、组和权限。用户可以增加和删除任何 Django 模型。可以创建组,也可以为用户和组分配权限。用户操作模型的日志也会在管理后台中保存和显示。

创建用户

你可以在管理后台主页的 “认证和权限” 部分看到 “用户” 链接。“增加用户”的管理界面和其他标准的管理后台不同的是:你需要先输入一个新用户名和新密码,然后才能编辑新用户的剩余字段。

注意:如果你希望某个用户账号可以在 Django 管理后台创建用户,你将需要给用户“创建”和“修改”权限。如果账号只有“创建”权限但没有“修改”权限,那么这个账号将不能增加用户。为什么?因为你有权限添加用户,那么你就拥有添加超级管理员的权利,而超级管理员就可以修改其他用户。因此,Django 需要添加和修改权限作为安全措施。

要考虑如何允许用户去管理权限。如果你允许普通管理员编辑用户,这和给他们超级管理员权限一样。因为他们有权利提升用户权限,包括他们自己。

更改密码

用户密码不显示在管理界面中(也不在数据库中保存),但会显示 password storage details 。这个信息中包含一个指向密码修改表单的链接,该表单允许管理员修改用户密码。

Back to Top