如何使用 Django 提供的 CSRF 防护功能

要在你的视图中利用 CSRF 保护,请遵循以下步骤:

  1. CSRF 中间件默认在 MIDDLEWARE 配置中被激活。如果你覆盖了这个配置,请记住 'django.middleware.csrf.CsrfViewMiddleware' 应该排在任何假设 CSRF 攻击已经被处理的视图中间件之前。

    如果你禁用了它,这并不推荐,你可以使用 csrf_protect() 对你想要保护的特定视图进行保护(见下文)。

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

    1. <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 配置是否启用。

推荐的令牌来源是 csrftoken cookie,如果你已经为你的视图启用了上文所述的 CSRF 保护,则会设置该 cookie。

CSRF 令牌 cookie 默认命名为 csrftoken,但你可以通过 CSRF_COOKIE_NAME 配置来控制 cookie 的名称。

你可以通过这样的方式获得令牌:

  1. function getCookie(name) {
  2. let cookieValue = null;
  3. if (document.cookie && document.cookie !== '') {
  4. const cookies = document.cookie.split(';');
  5. for (let i = 0; i < cookies.length; i++) {
  6. const cookie = cookies[i].trim();
  7. // Does this cookie string begin with the name we want?
  8. if (cookie.substring(0, name.length + 1) === (name + '=')) {
  9. cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
  10. break;
  11. }
  12. }
  13. }
  14. return cookieValue;
  15. }
  16. const csrftoken = getCookie('csrftoken');

上述代码可以通过使用 JavaScript Cookie 库 代替 getCookie 来简化:

  1. const csrftoken = Cookies.get('csrftoken');

备注

CSRF令牌也以掩码形式存在于DOM中,但仅当在模板中明确包含 csrf_token 时才存在。Cookie 包含规范的、未掩码的令牌。 CsrfViewMiddleware 将接受任一种令牌。但是,为了防止 BREACH 攻击,建议使用掩码令牌。

警告

如果你的视图没有渲染包含 csrf_token 模板标签的模板,Django 可能不会设置 CSRF 令牌 cookie。这种情况常见于表单被动态添加到页面的情况。针对这种情况,Django 提供了一个视图装饰器来强制设置 cookie: sure_csrf_cookie()

如果你激活了 CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY,你必须在你的 HTML 中包含 CSRF 令牌,并通过 JavaScript 从 DOM 中读取该令牌:

  1. {% csrf_token %}
  2. <script>
  3. const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
  4. </script>

在 AJAX 请求中设置令牌

最后,你需要在 AJAX 请求中设置头。使用 fetch() API:

  1. const request = new Request(
  2. /* URL */,
  3. {
  4. method: 'POST',
  5. headers: {'X-CSRFToken': csrftoken},
  6. mode: 'same-origin' // Do not send CSRF token to another domain.
  7. }
  8. );
  9. fetch(request).then(function(response) {
  10. // ...
  11. });

在 Jinja2 模板中使用 CSRF 防护

Django 的 Jinja2 模板后端在所有模板的上下文中添加了 {{ csrf_input }},相当于 Django 模板语言中的 {% csrf_token %}。例如:

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

在装饰器方法中使用

不要将 CsrfViewMiddleware 作为全局保护添加,而是可以在需要保护的特定视图上使用具有完全相同功能的 csrf_protect() 装饰器。它必须 同时 用于在输出中插入 CSRF 令牌的视图和接受 POST 表单数据的视图。 (这些通常是相同的视图函数,但不总是)。

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

处理被拒绝的请求

默认情况下,如果传入的请求未能通过由中间件 CsrfViewMiddleware 执行的检查,用户会收到“403 Forbidden”响应。 这通常只应该出现在真正的跨站点请求伪造时,或者由于编程错误,CSRF 令牌没有被包含在 POST 表单中。

然而,错误页面不是很友好,所以你可能想提供自己的视图来处理这种情况。 要做到这一点,请在`settings.py`中的配置`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() 装饰器:

  1. from django.views.decorators.cache import cache_page
  2. from django.views.decorators.csrf import csrf_protect
  3. @cache_page(60 * 15)
  4. @csrf_protect
  5. def my_view(request):
  6. ...

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

CSRF 防护与测试

``CsrfViewMiddleware``通常会对测试视图函数造成很大的阻碍,因为每个POST请求都需要发送CSRF令牌。因此,Django用于测试的HTTP客户端已经修改了,为请求设置了一个标志,放松了中间件和` ‘ csrf_protect ‘ `装饰器,使它们不再拒绝请求。在其他方面(例如发送cookie等),它们的行为是相同的。

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

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

边缘案例

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

在较少视图中禁用 CSRF 防护

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

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

当`CsrfViewMiddleware.process_view()``不被使用时设置令牌

有些情况下,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() 表示其中需要保护的路径。例如:

  1. from django.views.decorators.csrf import csrf_exempt, csrf_protect
  2. @csrf_exempt
  3. def my_view(request):
  4. @csrf_protect
  5. def protected_path(request):
  6. do_something()
  7. if some_condition():
  8. return protected_path(request)
  9. else:
  10. do_something_else()

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

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

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

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

因为开发人员可以关闭 CsrfViewMiddleware,所以 contrib 应用程序中的所有相关视图都使用 csrf_protect 装饰器来确保这些应用程序针对 CSRF 的安全性。 建议需要相同保证的其他可重用应用程序的开发人员也在其视图上使用 csrf_protect 装饰器。