使用 Django 的验证系统

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

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

User 对象

User 对象是认证系统的核心。它们通常代表与你网站进行交互的人,并用于启用诸如限制访问、注册用户配置文件、将内容与创建者关联等功能。在 Django 的认证框架中只存在一个用户类,即 'superusers' 或管理员 'staff' 用户只是具有特殊属性设置的用户对象,而不是不同类别的用户对象。

默认用户的主要属性是:

请查看 完整的 API 文档 以获取详细的参考信息,接下来的文档更偏向于任务导向。

创建用户

创建用户的最直接方法是使用包含的 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 管理界面,你还可以 以交互方式创建用户

创建超级用户

使用 createsuperuser 命令创建超级用户:

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

你将被要求输入密码。输入密码后,用户将立即创建。如果你不使用 --username--email 选项,它将提示你输入这些值。

更改密码

Django 不会在用户模型上存储原始(明文)密码,而只会存储散列值(详细信息请参阅 有关密码管理的文档)。因此,请不要尝试直接操作用户的密码属性。这就是在创建用户时使用辅助函数的原因。

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

manage.py changepassword *username* 提供了一种从命令行更改用户密码的方法。它会提示您更改指定用户的密码,你必须输入两次密码。如果它们都匹配,新密码将立即更改。如果你不提供用户名,该命令将尝试更改与当前系统用户匹配的用户名的密码。

你也可以通过编程方式更改密码,使用 set_password()

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

如果你已安装了 Django 管理员界面,你也可以在 身份验证系统的管理员页面 上更改用户的密码。

Django 还提供了可以用于允许用户更改自己密码的 视图表单

更改用户的密码将注销其所有会话。请参阅 password-change-session-invalidation 以获取详细信息。

验证用户

authenticate(request=None, **credentials)[源代码]
aauthenticate(request=None, **credentials)

异步版本aauthenticate()

使用 authenticate() 来验证一组凭据。它以关键字参数的形式接受凭据,对于默认情况下,是 usernamepassword,然后检查它们与每个 认证后端 进行匹配,如果凭据对于某个后端是有效的,则返回一个 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

Changed in Django 5.0:

aauthenticate() 函数已添加。

权限和认证

Django 自带了一个内置的权限系统。它提供了一种将权限分配给特定用户和用户组的方式。

它被 Django 管理站点使用,但欢迎你在自己的代码中使用它。

Django 管理站点使用权限如下:

  • 查看对象的访问权限仅限于具有该类型对象的 "view" 或 "change" 权限的用户。
  • 查看 "添加" 表单并添加对象的访问权限仅限于具有该类型对象的 "add" 权限的用户。
  • 访问更改列表、查看 "更改" 表单和更改对象的权限仅限于具有该类型对象的 "change" 权限的用户。
  • 删除对象的权限仅限于具有该类型对象的 "delete" 权限的用户。

权限不仅可以按对象类型设置,还可以按特定对象实例设置。通过使用 ModelAdmin 类提供的 has_view_permission()has_add_permission()has_change_permission()has_delete_permission() 方法,可以为相同类型的不同对象实例自定义权限。

User 对象有两个多对多字段:groupsuser_permissionsUser 对象可以以与任何其他 Django 模型 相同的方式访问其相关对象:

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.auth 添加到 INSTALLED_APPS 后首次运行 migrate 时,将为所有先前安装的模型创建默认权限,以及在此时安装的任何新模型。此后,每次运行 manage.py migrate 时(创建权限的函数与 post_migrate 信号相关联),都会为新模型创建默认权限。

假设你有一个带有 app_labelfoo 的应用程序,并且有一个名为 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')

Permission 模型很少直接访问。

django.contrib.auth.models.Group 模型是一种通用的方式,用于对用户进行分类,以便可以为这些用户分配权限或其他标签。一个用户可以属于任意数量的组。

属于组的用户自动拥有分配给该组的权限。例如,如果组 Site editors 具有权限 can_edit_home_page,那么该组中的任何用户都将具有该权限。

除了权限外,组是一种方便的方式,用于对用户进行分类并为他们提供一些标签或扩展功能。例如,你可以创建一个组 'Special users',然后编写代码,可以让他们访问你网站的会员专区,或者发送给他们会员专用的电子邮件消息。

以编程方式创建权限

虽然 自定义权限 可以在模型的 Meta 类中定义,但你也可以直接创建权限。例如,你可以为 myapp 中的 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,
)

然后可以通过将权限分配给 Useruser_permissions 属性或 Grouppermissions 属性来分配该权限。

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

如果你想为代理模型创建 权限,请将 for_concrete_model=False 传递给 ContentTypeManager.get_for_model() 以获取适当的 ContentType

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

权限缓存

ModelBackend 在第一次需要获取权限进行权限检查后,会将权限缓存到用户对象上。这通常在请求-响应循环中是没问题的,因为权限通常不会在添加后立即进行检查(例如,在管理界面中)。如果你正在添加权限并立即在测试或视图中进行检查,最简单的解决方法是重新从数据库中获取用户。例如:

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 使用 会话 和中间件将身份验证系统与 请求对象 钩连在一起。

这些提供了每个请求上的 request.user 属性和 request.auser 异步方法,表示当前用户。如果当前用户未登录,这个属性将被设置为 AnonymousUser 的实例,否则它将是 User 的实例。

你可以使用 is_authenticated 来区分它们,如下所示:

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

或者在一个异步视图中:

user = await request.auser()
if user.is_authenticated:
    # Do something for authenticated users.
    ...
else:
    # Do something for anonymous users.
    ...
Changed in Django 5.0:

已添加了 HttpRequest.auser() 方法。

如何登录用户

如果你有一个已经经过认证的用户,想要将其附加到当前会话中,可以使用 login() 函数来实现。

login(request, user, backend=None)[源代码]
alogin(request, user, backend=None)

异步版本alogin()

要从视图中登录用户,请使用 login()。它接受一个 HttpRequest 对象和一个 User 对象。login() 会使用 Django 的会话框架将用户的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.
        ...
Changed in Django 5.0:

alogin() 函数已添加。

选择认证后端

当用户登录时,用户的 ID 和用于认证的后端被保存在用户的会话中。这允许相同的 认证后端 在将来的请求中获取用户的详细信息。要保存在会话中的认证后端是按以下方式选择的:

  1. 使用可选的 backend 参数的值,如果提供了的话。
  2. 使用 user.backend 属性的值,如果存在的话。这允许将 authenticate()login() 配对使用: authenticate() 会在返回的用户对象上设置 user.backend 属性。
  3. 如果只有一个 AUTHENTICATION_BACKENDS 中的后端,则使用其中的后端。
  4. 否则,抛出一个异常。

在情况 1 和情况 2 中,backend 参数的值或 user.backend 属性应该是一个点分路径字符串(类似于 AUTHENTICATION_BACKENDS 中的路径),而不是实际的后端类。

如何登出用户

logout(request)[源代码]
alogout(request)

异步版本alogout()

要登出通过 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() 时,当前请求的会话数据将被完全清除。所有现有的数据都会被移除。这是为了防止其他人使用同一个网络浏览器登录并访问之前用户的会话数据。如果你想将任何内容放入会话中,以便用户在注销后立即使用,请在调用 logout() 之后执行这个操作。

Changed in Django 5.0:

alogout() 函数已添加。

限制访问仅限已登录用户

原始方式

限制访问页面的原始方法是检查 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(f"{settings.LOGIN_URL}?next={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 也接受视图函数名称和 命名的 URL 模式。这允许你在 URL 配置中自由重新映射你的登录视图,而无需更新该设置。

备注

login_required 装饰器并不检查用户的 is_active 标志,但默认的 AUTHENTICATION_BACKENDS 会拒绝非活跃用户。

参见

如果你正在为 Django 的管理编写自定义视图(或者需要与内置视图使用相同的授权检查),你可能会发现 django.contrib.admin.views.decorators.staff_member_required() 装饰器是一个有用的替代选择,而不是使用 login_required()

Changed in Django 5.1:

已添加对包装异步视图函数的支持。

LoginRequiredMixin 混合类

当使用 基于类的视图 时,你可以通过使用 LoginRequiredMixin 来实现与 login_required 相同的行为。这个混合类应该在继承列表的最左边位置。

class LoginRequiredMixin[源代码]

如果一个视图使用了这个混合类,所有非经过身份验证的用户的请求将被重定向到登录页面或显示 HTTP 403 禁止访问的错误,这取决于 raise_exception 参数的设置。

你可以设置 AccessMixin 的任何参数来自定义未经授权用户的处理方式:

from django.contrib.auth.mixins import LoginRequiredMixin


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

备注

与``login_required``装饰器一样,这个混合类也不会检查用户的``is_active``标志,但默认的 AUTHENTICATION_BACKENDS 会拒绝非活跃用户。

The login_not_required decorator

New in Django 5.1.

When LoginRequiredMiddleware is installed, all views require authentication by default. Some views, such as the login view, may need to disable this behavior.

login_not_required()[源代码]

Allows unauthenticated requests to this view when LoginRequiredMiddleware is installed.

限制只有经过测试的已登录用户才能访问

要基于特定权限或其他测试来限制访问,你可以基本上按照前一节所述的方法进行操作。

你可以直接在视图中对 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() 接受一个必需的参数,即一个可调用对象,该对象接受一个 User 对象,并在用户被允许查看页面时返回 True。请注意,user_passes_test() 不会自动检查 User 是否是匿名用户。

user_passes_test() 接受两个可选参数:

login_url
允许你指定未通过测试的用户将被重定向到的 URL。如果你没有指定一个 URL,它可能是一个登录页面,并且默认为 settings.LOGIN_URL
redirect_field_name
login_required() 类似。将其设置为 None 将其从 URL 中移除,如果你将不通过测试的用户重定向到一个没有 "下一页" 的非登录页面,你可能会想这样做。

例如:

@user_passes_test(email_check, login_url="/login/")
def my_view(request): ...
Changed in Django 5.1:

Support for wrapping asynchronous view functions and using asynchronous test callables was added.

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() 方法,以便混合类使用不同名称的函数进行检查(而不是 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)[源代码]

It's a relatively common task to check whether a user has a particular permission. For that reason, Django provides a shortcut for that case: the permission_required() decorator:

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 )。

该装饰器还可以接受一个权限的可迭代对象,此时用户必须具备所有这些权限才能访问视图。

请注意,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 默认为 settings.LOGIN_URL

如果提供了 raise_exception 参数,装饰器将引发 PermissionDenied 异常,而不是重定向到登录页面,从而触发 403(HTTP Forbidden)视图

如果你想使用 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 且已登录用户没有所有所需权限时发生重定向循环。

Changed in Django 5.1:

已添加对包装异步视图函数的支持。

PermissionRequiredMixin Mixin

要将权限检查应用于 基于类的视图,你可以使用 PermissionRequiredMixin

class PermissionRequiredMixin[源代码]

这个混合类,就像 permission_required 装饰器一样,检查访问视图的用户是否具有所有给定的权限。你应该使用 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()[源代码]

返回一个被混合类使用的权限名称的可迭代对象。默认为 permission_required 属性,如果需要会将其转换为元组。

has_permission()[源代码]

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

在基于类的视图中重定向未经授权的请求

为了简化在 基于类的视图 中处理访问限制,可以使用 AccessMixin 来配置在拒绝访问时视图的行为。已认证的用户将被拒绝访问,并收到 HTTP 403 禁止访问的响应。匿名用户将根据 raise_exception 属性的设置,被重定向到登录页面或显示 HTTP 403 禁止访问的响应。

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()[源代码]

返回未通过测试的用户将被重定向到的URL。如果设置了 login_url,则返回它,否则返回 settings.LOGIN_URL

get_permission_denied_message()[源代码]

raise_exceptionTrue 时,可以使用此方法来控制传递给错误处理程序以显示给用户的错误消息。默认情况下返回 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 默认包含的密码更改视图,包括 PasswordChangeViewdjango.contrib.auth 管理界面中的 user_change_password 视图,会使用新的密码哈希更新会话,以便用户更改自己的密码不会注销自己。如果你有自定义的密码更改视图并希望具有类似的行为,可以使用 update_session_auth_hash() 函数。

update_session_auth_hash(request, user)[源代码]
aupdate_session_auth_hash(request, user)

异步版本aupdate_session_auth_hash()

这个函数接受当前请求和已更新的用户对象,新的会话哈希将从中派生,并适当地更新会话哈希。它还会轮换会话密钥,以使被盗的会话 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:
        ...
Changed in Django 5.0:

aupdate_session_auth_hash() 函数已添加。

备注

由于 get_session_auth_hash() 基于 SECRET_KEY,在更新站点以使用新的密钥时,必须轮换密钥值,以避免使现有会话失效。有关详细信息,请参阅 SECRET_KEY_FALLBACKS

认证视图

Django 提供了多个视图,你可以用来处理登录、注销和密码管理。这些视图使用了 内置的身份验证表单,但你也可以传入自己的表单。

Django 不提供身份验证视图的默认模板。你应该为想要使用的视图创建自己的模板。模板上下文在每个视图中都有文档记录,详见 所有认证视图

使用视图

有多种方法可以在项目中实现这些视图。最简单的方法是在你自己的 URL 配置中包含提供的 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模式的详细信息,请参阅 URL文档

如果你想要更多控制你的 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 名称: login

请参阅 URL 文档 以获取有关使用命名 URL 模式的详细信息。

方法和属性

template_name

用于显示用户登录视图的模板名称。默认为 registration/login.html

next_page

登录后重定向的 URL。默认为 LOGIN_REDIRECT_URL

redirect_field_name

包含登录后重定向 URL 的 GET 字段名称。默认为 next。如果传递了给定的 GET 参数,将覆盖 get_default_redirect_url() URL。

authentication_form

用于身份验证的可调用对象(通常是一个表单类)。默认为 AuthenticationForm

extra_context

一个包含将添加到传递给模板的默认上下文数据的字典。

redirect_authenticated_user

一个布尔值,控制是否对已经认证的用户访问登录页面进行重定向,就好像他们刚刚成功登录一样。默认为 False

警告

如果启用了 redirect_authenticated_user,其他网站可以通过请求重定向到你网站上的图像文件的 URL 来确定他们的访问者是否在你的站点上进行了身份验证。为了避免这种 "社交媒体指纹识别" 信息泄漏,将所有图像和您的站点图标托管在一个单独的域上。

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

success_url_allowed_hosts

一个主机集合,用于登录后的重定向,除了 request.get_host(),默认为空 set

get_default_redirect_url()[源代码]

返回登录后重定向的URL。如果设置了默认实现会解析并返回 next_page,否则返回 LOGIN_REDIRECT_URL

以下是 LoginView 所做的事情:

  • 如果通过 GET 调用,它会显示一个登录表单,该表单将数据 POST 到相同的 URL。稍后会详细介绍这一点。
  • 如果通过用户提交的凭据调用 POST,它会尝试登录用户。如果登录成功,视图将重定向到 next 参数指定的 URL。如果未提供 next,它将重定向到 settings.LOGIN_REDIRECT_URL (默认为 /accounts/profile/ )。如果登录不成功,它会重新显示登录表单。

你需要提供登录模板的 HTML,默认情况下命名为 registration/login.html。该模板将接收四个模板上下文变量:

  • form: 一个代表 AuthenticationFormForm 对象。
  • next: 登录成功后要重定向的 URL。这可能包含查询字符串。
  • site: 根据 SITE_ID 设置,表示当前的 Site。如果你没有安装站点框架,它将设置为 RequestSite 的实例,该实例从当前的 HttpRequest 获取站点名称和域名。
  • site_name: 用于别名 site.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 指定包含登录后重定向 URL 的 GET 字段的名称。默认情况下,该字段称为 next

以下是一个示例 registration/login.html 模板,你可以将其用作起点。它假定你有一个定义了 content 块的 base.html 模板:

{% 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 set up the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>

{% endblock %}

如果你已经自定义了身份验证(参见 自定义身份验证),你可以通过设置 authentication_form 属性来使用自定义的身份验证表单。这个表单必须在其 __init__() 方法中接受一个 request 关键字参数,并提供一个返回经过身份验证的用户对象的 get_user() 方法(此方法仅在表单验证成功后调用)。

class LogoutView[源代码]

POST 请求中登出用户。

URL 名称: logout

属性:

next_page

登出后重定向的 URL。默认为 LOGOUT_REDIRECT_URL

template_name

在用户注销后显示的模板的全名。默认为 registration/logged_out.html

redirect_field_name

GET 字段的名称,包含在登出后重定向的 URL。默认为 'next'。如果传递了给定的 GET 参数,则会覆盖 next_page URL。

extra_context

一个包含将添加到传递给模板的默认上下文数据的字典。

success_url_allowed_hosts

一个 set,除了 request.get_host() 外,用于在注销后安全重定向的主机。默认为空的 set

模板上下文:

  • title:本地化的字符串 "已注销"。
  • site: 根据 SITE_ID 设置,表示当前的 Site。如果你没有安装站点框架,它将设置为 RequestSite 的实例,该实例从当前的 HttpRequest 获取站点名称和域名。
  • site_name: 用于别名 site.name。如果你没有安装站点框架,它将设置为 request.META['SERVER_NAME'] 的值。有关站点的更多信息,请参阅 “站点”框架
logout_then_login(request, login_url=None)[源代码]

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

URL 名称: 未提供默认 URL

可选参数:

  • login_url: 要重定向到的登录页面的 URL。如果未提供,默认为 settings.LOGIN_URL
class PasswordChangeView[源代码]

URL 名称: password_change

允许用户更改他们的密码。

属性:

template_name

用于显示密码更改表单的模板的完整名称。如果未提供,默认为 registration/password_change_form.html

success_url

在成功更改密码后重定向到的 URL。默认为 'password_change_done'

form_class

一个自定义的“更改密码”表单,必须接受一个 user 关键字参数。该表单负责实际更改用户的密码。默认为 PasswordChangeForm

extra_context

一个包含将添加到传递给模板的默认上下文数据的字典。

模板上下文:

  • form:密码更改表单(参见上面的 form_class)。
class PasswordChangeDoneView[源代码]

URL 名称: password_change_done

用户成功更改密码后显示的页面。

属性:

template_name

要使用的模板的完整名称。如果未提供,默认为 registration/password_change_done.html

extra_context

一个包含将添加到传递给模板的默认上下文数据的字典。

class PasswordResetView[源代码]

URL 名称: password_reset

允许用户通过生成一次性使用的链接来重置密码,并将该链接发送到用户注册的电子邮件地址。

如果满足以下条件,此视图将发送电子邮件:

  • 提供的电子邮件地址存在于系统中。
  • 所请求的用户处于活动状态( User.is_activeTrue )。
  • 所请求的用户具有可用的密码。被标记为不可用密码的用户(参见 set_unusable_password() )不允许请求密码重置,以防止在使用外部身份验证源如 LDAP 时被滥用。

如果不满足上述任何条件,将不会发送电子邮件,但用户也不会收到任何错误消息。这可以防止信息泄漏给潜在的攻击者。如果您想在这种情况下提供错误消息,可以子类化 PasswordResetForm 并使用 form_class 属性。

备注

请注意,发送电子邮件会耗费额外的时间,因此由于重置请求的持续时间与不存在的电子邮件地址的重置请求的持续时间之间存在差异,您可能会容易受到电子邮件地址枚举时序攻击的影响。为了减少开销,您可以使用第三方包,允许异步发送电子邮件,例如 django-mailer

属性:

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

用于生成带有密码重置链接的 text/html 多部分电子邮件的模板的完整名称。默认情况下,不会发送 HTML 电子邮件。

extra_email_context

一个包含在电子邮件模板中可用的上下文数据的字典。它可以用于覆盖下面列出的默认模板上下文值,例如 domain

模板上下文:

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

电子邮件模板上下文:

  • emailuser.email 的别名
  • user :根据 email 表单字段,表示当前的 User。只有活动用户才能重置他们的密码( User.is_active is True )。
  • site_name: 用于别名 site.name。如果你没有安装站点框架,它将设置为 request.META['SERVER_NAME'] 的值。有关站点的更多信息,请参阅 “站点”框架
  • domainsite.domain 的别名。如果你没有安装站点框架,这将设置为 request.get_host() 的值。
  • protocol :http 或 https
  • uid :使用 base 64 编码过的用户主键。
  • 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 名称: password_reset_done

在向用户发送了重置密码链接后显示的页面。如果 PasswordResetView 没有显式设置 success_url URL,则默认调用此视图。

备注

如果提供的电子邮件地址在系统中不存在,用户处于非活动状态,或者密码不可用,用户仍然会被重定向到此视图,但不会发送电子邮件。

属性:

template_name

要使用的模板的完整名称。如果未提供,默认为 registration/password_reset_done.html

extra_context

一个包含将添加到传递给模板的默认上下文数据的字典。

class PasswordResetConfirmView[源代码]

URL 名称: password_reset_confirm

提供一个用于输入新密码的表单。

来自 URL 的关键字参数:

  • uidb64 :以 base 64 编码的用户 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

作为密码重置 URL 的一部分显示的令牌参数。默认为 'set-password'

模板上下文:

  • form:用于设置新用户密码的表单(参见上面的 form_class)。
  • validlink:布尔值,如果链接(uidb64token 的组合)有效或尚未使用,则为 True。
class PasswordResetCompleteView[源代码]

URL 名称: 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_nameGET 字段包含的登录后跳转 URL 的参数名称。如果传递给定的 GET 参数,将会覆盖 next

内置表单

如果您不想使用内置视图,但希望方便地不必编写用于此功能的表单,身份验证系统提供了位于 django.contrib.auth.forms 中的几个内置表单:

备注

内置身份验证表单对其正在处理的用户模型进行了某些假设。如果您使用了 自定义用户模型,可能需要为身份验证系统定义自己的表单。有关更多信息,请参阅关于 在自定义用户模型中使用内置身份验证表单 的文档。

class AdminPasswordChangeForm[源代码]

A form used in the admin interface to change a user's password, including the ability to set an unusable password, which blocks the user from logging in with password-based authentication.

user 作为第一个位置参数。

Changed in Django 5.1:

Option to disable (or reenable) password-based authentication was added.

class AdminUserCreationForm[源代码]
New in Django 5.1.1.

A form used in the admin interface to create a new user. Inherits from UserCreationForm.

It includes an additional usable_password field, enabled by default. If usable_password is enabled, it verifies that password1 and password2 are non empty and match, validates the password using validate_password(), and sets the user's password using set_password(). If usable_password is disabled, no password validation is done, and password-based authentication is disabled for the user by calling set_unusable_password().

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 BaseUserCreationForm[源代码]

用于创建新用户的 ModelForm。如果您需要自定义用户创建表单,则推荐使用此基类。

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

class PasswordChangeForm[源代码]

用于允许用户更改密码的表单。

class PasswordResetForm[源代码]

用于生成并发送一次性使用链接以重置用户密码的表单。

send_mail(subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None)[源代码]

Uses the arguments to send an EmailMultiAlternatives. Can be overridden to customize how the email is sent to the user. If you choose to override this method, be mindful of handling potential exceptions raised due to email sending failures.

参数:
  • subject_template_name -- 主题的模板。
  • email_template_name -- 电子邮件正文的模板。
  • context -- 传递给 subject_templateemail_templatehtml_email_template (如果不为 None )的上下文。
  • from_email -- 发送者的电子邮件地址。
  • to_email -- 请求者的电子邮件地址。
  • html_email_template_name -- HTML 正文的模板;默认为 None,在这种情况下发送纯文本电子邮件。

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

class SetPasswordForm[源代码]

一个允许用户在不输入旧密码的情况下更改密码的表单。

class UserChangeForm[源代码]

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

class UserCreationForm[源代码]

继承自 BaseUserCreationForm。为了防止与类似的用户名混淆,该表单不允许仅在大小写上不同的用户名。

模板中的认证数据

当您使用 RequestContext 时,当前已登录的用户及其权限将在 模板上下文 中提供。

技术细节

从技术上讲,只有在使用 RequestContext 并启用了 'django.contrib.auth.context_processors.auth' 上下文处理器时,这些变量才会在模板上下文中提供。它在默认生成的设置文件中已经存在。有关更多信息,请参阅 RequestContext 文档

用户

在渲染模板时, 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() 的代理。例如,要检查已登录用户是否在 foo 应用程序中有任何权限:

{% 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 %}

在 admin 中管理用户

当您同时安装了 django.contrib.admindjango.contrib.auth 时,管理界面提供了一种方便的方式来查看和管理用户、组和权限。用户可以像任何 Django 模型一样创建和删除。可以创建组,并将权限分配给用户或组。还会存储和显示在管理界面内对模型的用户编辑记录。

创建用户

You should see a link to "Users" in the "Auth" section of the main admin index page. The "Add user" admin page is different than standard admin pages in that it requires you to choose a username and password before allowing you to edit the rest of the user's fields. Alternatively, on this page, you can choose a username and disable password-based authentication for the user.

还要注意:如果您希望用户帐户能够使用 Django 管理站点创建用户,您需要为其分配添加用户和更改用户(即“添加用户”和“更改用户”权限)。如果帐户有添加用户的权限但没有更改用户的权限,那么该帐户将无法添加用户。为什么?因为如果您有添加用户的权限,那么您就有了创建超级用户的权力,然后超级用户可以修改其他用户。因此,Django 要求同时具备添加和更改权限作为一种轻微的安全措施。

在允许用户管理权限时要谨慎。如果您授予非超级用户编辑用户的权限,这实际上与授予他们超级用户状态相同,因为他们将能够提升用户的权限,包括他们自己!

更改密码

User passwords are not displayed in the admin (nor stored in the database), but the password storage details are displayed. Included in the display of this information is a link to a password change form that allows admins to change or unset user passwords.

Back to Top