如何使用 Django 的 CSRF 保护

要利用视图中的 CSRF 保护,请按照以下步骤操作

  1. CSRF 中间件在 MIDDLEWARE 设置中默认启用。如果覆盖此设置,请记住 'django.middleware.csrf.CsrfViewMiddleware' 应位于任何假设 CSRF 攻击已被处理的视图中间件之前。

    如果禁用了它(不建议这样做),则可以使用 csrf_protect() 在要保护的特定视图上(见下文)。

  2. 在任何使用 POST 表单的模板中,如果表单用于内部 URL,请在 <form> 元素内使用 csrf_token 标签,例如:

    <form method="post">{% csrf_token %}
    

    对于目标为外部 URL 的 POST 表单,不应执行此操作,因为这会导致 CSRF 令牌泄漏,从而导致漏洞。

  3. 在相应的视图函数中,确保使用 RequestContext 渲染响应,以便 {% csrf_token %} 可以正常工作。如果您使用的是 render() 函数、通用视图或 contrib 应用,则您已经覆盖了,因为这些都使用 RequestContext

使用 AJAX 的 CSRF 保护

虽然上述方法可用于 AJAX POST 请求,但它有一些不便之处:您必须记住在每次 POST 请求中将 CSRF 令牌作为 POST 数据传递。出于这个原因,存在另一种方法:在每个 XMLHttpRequest 上,将自定义 X-CSRFToken 标头(如 CSRF_HEADER_NAME 设置中指定)设置为 CSRF 令牌的值。这通常更容易,因为许多 JavaScript 框架提供了允许在每个请求上设置标头的钩子。

首先,您必须获取 CSRF 令牌。如何获取取决于 CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY 设置是否启用。

在 AJAX 请求上设置令牌

最后,您需要在 AJAX 请求上设置标头。使用 fetch() API

const request = new Request(
    /* URL */,
    {
        method: 'POST',
        headers: {'X-CSRFToken': csrftoken},
        mode: 'same-origin' // Do not send CSRF token to another domain.
    }
);
fetch(request).then(function(response) {
    // ...
});

在 Jinja2 模板中使用 CSRF 保护

Django 的 Jinja2 模板后端将 {{ csrf_input }} 添加到所有模板的上下文,这等效于 Django 模板语言中的 {% csrf_token %}。例如

<form method="post">{{ csrf_input }}

使用装饰器方法

与其将 CsrfViewMiddleware 作为全面保护,不如使用 csrf_protect() 装饰器,它具有完全相同的功能,用于需要保护的特定视图。它必须同时用于插入 CSRF 令牌的视图和接受 POST 表单数据的视图。(这些通常是同一个视图函数,但并非总是如此)。

不建议单独使用装饰器,因为如果您忘记使用它,则会出现安全漏洞。“双保险”策略(同时使用两者)很好,并且开销很小。

处理被拒绝的请求

默认情况下,如果传入请求未通过 CsrfViewMiddleware 执行的检查,则会向用户发送“403 禁止访问”响应。这通常只会在存在真正的跨站点请求伪造或由于编程错误而未将 CSRF 令牌包含在 POST 表单中时才会看到。

但是,错误页面不太友好,因此您可能希望提供自己的视图来处理此情况。为此,请设置 CSRF_FAILURE_VIEW 设置。

CSRF 故障会作为警告记录到 django.security.csrf 记录器。

使用 CSRF 保护和缓存

如果 csrf_token 模板标签被模板使用(或 get_token 函数以其他方式调用),则 CsrfViewMiddleware 会将 cookie 和 Vary: Cookie 标头添加到响应中。这意味着如果按照说明使用中间件(UpdateCacheMiddleware 位于所有其他中间件之前),则中间件将与缓存中间件配合良好。

但是,如果您在单个视图上使用缓存装饰器,则 CSRF 中间件尚未能够设置 Vary 标头或 CSRF cookie,并且响应将在不设置任何一个的情况下被缓存。在这种情况下,对于需要插入 CSRF 令牌的任何视图,您应首先使用 django.views.decorators.csrf.csrf_protect() 装饰器

from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_protect


@cache_page(60 * 15)
@csrf_protect
def my_view(request): ...

如果您使用的是基于类的视图,则可以参考 装饰基于类的视图

测试和 CSRF 保护

由于需要 CSRF 令牌(必须与每个 POST 请求一起发送),因此 CsrfViewMiddleware 通常会对测试视图函数构成很大障碍。出于这个原因,Django 的测试 HTTP 客户端已修改为在请求上设置标志,从而放宽中间件和 csrf_protect 装饰器,使其不再拒绝请求。在其他所有方面(例如发送 cookie 等),它们的行为相同。

如果出于某种原因,您希望测试客户端执行 CSRF 检查,则可以创建强制执行 CSRF 检查的测试客户端实例

>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)

边缘情况

某些视图可能有一些特殊的要求,导致它们不符合此处设想的正常模式。在这些情况下,一些实用程序可能会有用。下一节将描述可能需要它们的场景。

仅对少数视图禁用 CSRF 保护

大多数视图都需要 CSRF 保护,但少数不需要。

解决方案:与其禁用中间件并对所有需要它的视图应用 csrf_protect,不如启用中间件并使用 csrf_exempt()

在未使用 CsrfViewMiddleware.process_view() 时设置令牌

在某些情况下,CsrfViewMiddleware.process_view 可能在您的视图运行之前没有运行——例如 404 和 500 处理程序——但您仍然需要表单中的 CSRF 令牌。

解决方案:使用 requires_csrf_token()

在不受保护的视图中包含 CSRF 令牌

可能有一些不受保护的视图已被 csrf_exempt 免除,但仍然需要包含 CSRF 令牌。

解决方案:使用 csrf_exempt() 后跟 requires_csrf_token()。(即 requires_csrf_token 应该是最内层的装饰器)。

仅对一条路径保护视图

一个视图仅在特定条件下需要 CSRF 保护,而在其他时间则不需要。

解决方案:对整个视图函数使用 csrf_exempt(),并对其中需要保护的路径使用 csrf_protect()。示例

from django.views.decorators.csrf import csrf_exempt, csrf_protect


@csrf_exempt
def my_view(request):
    @csrf_protect
    def protected_path(request):
        do_something()

    if some_condition():
        return protected_path(request)
    else:
        do_something_else()

保护使用 AJAX 但没有 HTML 表单的页面

一个页面通过 AJAX 发出 POST 请求,并且该页面没有带有 csrf_token 的 HTML 表单,这会导致发送所需的 CSRF cookie。

解决方案:在发送页面的视图上使用 ensure_csrf_cookie()

在可重用应用程序中使用 CSRF 保护

由于开发人员可以关闭 CsrfViewMiddleware,因此 contrib 应用程序中的所有相关视图都使用 csrf_protect 装饰器来确保这些应用程序免受 CSRF 的攻击。建议其他想要相同保证的可重用应用程序的开发人员也在其视图上使用 csrf_protect 装饰器。

返回顶部