如何使用 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 }}

在装饰器方法中使用

Rather than adding CsrfViewMiddleware as a blanket protection, you can use the csrf_protect() decorator, which has exactly the same functionality, on particular views that need the protection. It must be used both on views that insert the CSRF token in the output, and on those that accept the POST form data. (These are often the same view function, but not always).

不建议 单独使用装饰器,因为如果忘记使用,就会出现安全漏洞。“腰带和支架”的策略,两者同时使用也可以,而且会产生最小的开销。

处理被拒绝的请求

By default, a '403 Forbidden' response is sent to the user if an incoming request fails the checks performed by CsrfViewMiddleware. This should usually only be seen when there is a genuine Cross Site Request Forgery, or when, due to a programming error, the CSRF token has not been included with a POST form.

The error page, however, is not very friendly, so you may want to provide your own view for handling this condition. To do this, set the CSRF_FAILURE_VIEW setting.

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 防护与测试

The CsrfViewMiddleware will usually be a big hindrance to testing view functions, due to the need for the CSRF token which must be sent with every POST request. For this reason, Django's HTTP client for tests has been modified to set a flag on requests which relaxes the middleware and the csrf_protect decorator so that they no longer rejects requests. In every other respect (e.g. sending cookies etc.), they behave the same.

If, for some reason, you want the test client to perform CSRF checks, you can create an instance of the test client that enforces CSRF checks:

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

边缘案例

某些视图可能有不寻常的要求,这意味着它们不符合这里所设想的正常模式。在这些情况下,一些实用程序可能很有用。下一节将介绍可能需要它们的情况。

在较少视图中禁用 CSRF 防护

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

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

Setting the token when CsrfViewMiddleware.process_view() is not used

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

解决方法:使用 requests_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()

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

一个页面通过 AJAX 进行 POST 请求,而该页面并没有一个带有 csrf_token 的 HTML 表单,这将导致所需的 CSRF cookie 被发送。

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

在可复用应用中使用 CSRF 保护。

Because it is possible for the developer to turn off the CsrfViewMiddleware, all relevant views in contrib apps use the csrf_protect decorator to ensure the security of these applications against CSRF. It is recommended that the developers of other reusable apps that want the same guarantees also use the csrf_protect decorator on their views.