---
title: "跨站请求伪造保护"
version: 4.0
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/4.0/ref/csrf/
canonical: https://djangodocs.dev/zh-hans/4.0/ref/csrf/
---
# 跨站请求伪造保护

CSRF 中间件和模板标签提供了易于使用的保护，防止 [跨站请求伪造](https://www.squarefree.com/securitytips/web-developers.html#CSRF) 。 当一个恶意网站包含一个链接、一个表单按钮或一些 JavaScript，目的是在你的网站上执行一些操作，使用在浏览器中访问恶意网站的登录用户的凭证时，就会发生这种类型的攻击。 此外，还包括一种相关的攻击类型，“登录 CSRF”，即攻击网站欺骗用户的浏览器使用他人的凭证登录网站。

对 CSRF 攻击的第一道防线是确保 GET 请求（和其他“安全”方法，如 [**RFC 7231 Section 4.2.1**](https://datatracker.ietf.org/doc/html/rfc7231.html#section-4.2.1) 所定义的）没有副作用。通过“不安全”方法的请求，如 POST、PUT 和 DELETE，则可以通过以下步骤来保护。

## 如何使用它

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

1. CSRF 中间件默认在 [`MIDDLEWARE`](/zh-hans/4.0/ref/settings/#std-setting-MIDDLEWARE) 配置中被激活。如果你覆盖了这个配置，请记住 `'django.middleware.csrf.CsrfViewMiddleware'` 应该排在任何假设 CSRF 攻击已经被处理的视图中间件之前。

   如果你禁用了它，这并不推荐，你可以使用 [`csrf_protect()`](#django.views.decorators.csrf.csrf_protect) 对你想要保护的特定视图进行保护（见下文）。
2. 在任何使用 POST 表单的模板中，如果表单是针对内部 URL 的，请在 `<form>` 元素中使用 [`csrf_token`](/zh-hans/4.0/ref/templates/builtins/#std-templatetag-csrf_token) 标签，例如：

   ```html+django
   <form method="post">{% csrf_token %}
   ```

   对于以外部 URL 为目标的 POST 表单，不应该这样做，因为这会导致 CSRF 令牌泄露，从而导致漏洞。
3. 在相应的视图函数中，确保 [`RequestContext`](/zh-hans/4.0/ref/templates/api/#django.template.RequestContext) 用于渲染响应，这样 `{% csrf_token %}` 才能正常工作。如果你使用的是 [`render()`](/zh-hans/4.0/topics/http/shortcuts/#django.shortcuts.render) 函数、通用视图或 contrib 应用程序，你已经被覆盖了，因为这些都使用 `RequestContext`。

### AJAX

虽然上述方法可以用于 AJAX POST 请求，但它有一些不便之处：你必须记住在每个 POST 请求中都要把 CSRF 令牌作为 POST 数据传递进来。出于这个原因，有一种替代方法：在每个 XMLHttpRequest 上，设置一个自定义的 `X-CSRFToken` 头（由 [`CSRF_HEADER_NAME`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_HEADER_NAME) 设置指定）为 CSRF 标记的值。这通常比较容易，因为许多 JavaScript 框架提供了钩子，允许在每个请求中设置头。

首先，你必须获得 CSRF 令牌。如何做取决于 [`CSRF_USE_SESSIONS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_USE_SESSIONS) 和 [`CSRF_COOKIE_HTTPONLY`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_HTTPONLY) 配置是否启用。

#### 当 [`CSRF_USE_SESSIONS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_USE_SESSIONS) 和 [`CSRF_COOKIE_HTTPONLY`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_HTTPONLY) 为 `False` 时获取令牌

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

CSRF 令牌 cookie 默认命名为 `csrftoken`，但你可以通过 [`CSRF_COOKIE_NAME`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_NAME) 配置来控制 cookie 的名称。

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

```javascript
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
const csrftoken = getCookie('csrftoken');
```

上述代码可以通过使用 [JavaScript Cookie 库](https://github.com/js-cookie/js-cookie/) 代替 `getCookie` 来简化：

```javascript
const csrftoken = Cookies.get('csrftoken');
```

> **Note**
>
> CSRF 令牌也存在于 DOM 中，但只有在模板中使用 [`csrf_token`](/zh-hans/4.0/ref/templates/builtins/#std-templatetag-csrf_token) 明确包含时才会出现。Cookie 包含了规范的令牌；`CsrfViewMiddleware` 更倾向于使用 cookie 而不是 DOM 中的令牌。无论如何，如果 DOM 中存在令牌，你就能保证拥有 cookie，所以你应该使用 cookie！

> **Warning**
>
> 如果你的视图没有渲染包含 [`csrf_token`](/zh-hans/4.0/ref/templates/builtins/#std-templatetag-csrf_token) 模板标签的模板，Django 可能不会设置 CSRF 令牌 cookie。这种情况常见于表单被动态添加到页面的情况。针对这种情况，Django 提供了一个视图装饰器来强制设置 cookie： `sure_csrf_cookie()`。

#### 当 [`CSRF_USE_SESSIONS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_USE_SESSIONS) 或 [`CSRF_COOKIE_HTTPONLY`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_HTTPONLY) 为 `True` 时获取令牌

如果你激活了 [`CSRF_USE_SESSIONS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_USE_SESSIONS) 或 [`CSRF_COOKIE_HTTPONLY`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_HTTPONLY)，你必须在你的 HTML 中包含 CSRF 令牌，并通过 JavaScript 从 DOM 中读取该令牌：

```html+django
{% csrf_token %}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>
```

#### 在 AJAX 请求中设置令牌

最后，你需要在 AJAX 请求中设置头。使用 [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/fetch) API：

```javascript
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`](/zh-hans/4.0/topics/templates/#django.template.backends.jinja2.Jinja2) 模板后端在所有模板的上下文中添加了 `{{ csrf_input }}`，相当于 Django 模板语言中的 `{% csrf_token %}`。例如：

```html+jinja
<form method="post">{{ csrf_input }}
```

### 装饰器方法

与其添加 `CsrfViewMiddleware` 作为全面保护，不如在需要保护的特定视图上使用 `csrf_protect` 装饰器，它具有完全相同的功能。它必须用于 **同时** 在输出中插入 CSRF 令牌的视图和接受 POST 表单数据的视图。（这些通常是相同的视图函数，但并不总是如此）。

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

#### `csrf_protect(view)`

为视图提供 `CsrfViewMiddleware` 保护的装饰器。

用法：

```
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect

@csrf_protect
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)
```

如果你使用的是基于类的视图，你可以参考 [装饰基于类的视图](/zh-hans/4.0/topics/class-based-views/intro/#id1)。

## 被拒绝的请求

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

然而，错误页面不是很友好，所以你可能想提供自己的视图来处理这种情况。 要做到这一点，请设置 [`CSRF_FAILURE_VIEW`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_FAILURE_VIEW) 配置。

CSRF 失败会被记录为警告到 [django.security.csrf](/zh-hans/4.0/ref/logging/#django-security-logger) 记录器。

## 工作方式

CSRF 保护是基于以下几点：

1. 一个基于随机密钥值的 CSRF cookie，其他网站无法访问。

   这个 cookie 是由 `CsrfViewMiddleware` 设置的。如果在请求中还没有设置的话，那么它将与调用了 `django.middleware.csrf.get_token()` （内部用于获取 CSRF 令牌的函数）的每个响应一起发送。

   为了防止 [BREACH](http://breachattack.com/) 攻击，令牌不是简单的密钥，而是在密钥前面加上一个随机掩码，用来扰乱密钥。

   出于安全考虑，每次用户登录时都会改变密钥的值。
2. 一个隐藏的表单字段，名称为“csrfmiddlewaretoken”，存在于所有发出的 POST 表单中。这个字段的值也是密钥的值，但有一个掩码，这个掩码会被添加到字段中，并被用来扰乱字段。掩码在每次调用 `get_token()` 时都会重新生成，所以表单字段的值在每次响应时都会改变。

   这一部分是由模板标签来完成的。
3. 对于所有不使用 HTTP GET、HEAD、OPTIONS 或 TRACE 的传入请求，必须存在一个 CSRF cookie，并且“csrfmiddlewaretoken”字段必须存在且正确。如果不存在，用户将得到一个 403 错误。

   当验证“csrfmiddlewaretoken”字段值时，只有密钥，而不是完整的令牌，会与 cookie 值中的密钥进行比较。这允许使用不断变化的令牌。虽然每个请求都可能使用自己的令牌，但密钥对所有请求都是通用的。

   这个检查是由 `CsrfViewMiddleware` 完成的。
4. `CsrfViewMiddleware` 根据当前主机和 [`CSRF_TRUSTED_ORIGINS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS) 的设置，验证 [Origin header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin) ，如果是由浏览器提供的。这提供了对跨子域攻击的保护。
5. 此外，对于 HTTPS 请求，如果没有提供 `Origin` 头，`CsrfViewMiddleware` 会执行严格的来源检查。这意味着，即使一个子域可以设置或修改你的域名上的 cookie，它也不能强迫用户向你的应用程序发布，因为该请求不会来自你自己的确切域名。

   这也解决了在 HTTPS 下使用独立于会话的密钥时可能出现的中间人攻击问题，这是因为 HTTP `Set-Cookie` 头会被客户接受（不幸的是），即使他们在 HTTPS 下与一个网站对话。对 HTTP 请求不进行 Referer 检查，因为 HTTP 下 `Referer` 头的存在不够可靠）。

   如果设置了 [`CSRF_COOKIE_DOMAIN`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_DOMAIN) 设置，则会将 referer 与之进行比较。你可以通过包含一个前导点号来允许跨子域请求。例如，`CSRF_COOKIE_DOMAIN = '.example.com'` 将允许来自 `www.example.com` 和 `api.example.com` 的 POST 请求。如果没有设置，那么 referer 必须与 HTTP `Host` 头匹配。

   通过 [`CSRF_TRUSTED_ORIGINS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS) 设置，可以将接受的 referer 扩展到当前主机或 cookie 域之外。

> **New in Django 4.0**
>
> 如上所述，增加了 `Origin` 检查。

这确保了只有源自受信任域的表单才能用于 POST 回数据。

它故意忽略了 GET 请求（以及被 [**RFC 7231 Section 4.2.1**](https://datatracker.ietf.org/doc/html/rfc7231.html#section-4.2.1) 定义为“安全”的其他请求）。[**RFC 7231 Section 4.2.1**](https://datatracker.ietf.org/doc/html/rfc7231.html#section-4.2.1) 将 POST、PUT 和 DELETE 定义为“不安全”，所有其他方法也被认为是不安全的，以获得最大的保护。

CSRF 保护不能防止中间人攻击，所以使用 [HTTPS](/zh-hans/4.0/topics/security/#security-recommendation-ssl) 与 [HTTP 严格传输安全](/zh-hans/4.0/ref/middleware/#http-strict-transport-security)。它还假设 [验证 HOST 头](/zh-hans/4.0/topics/security/#host-headers-virtual-hosting) 和你的网站上没有任何 [跨站脚本漏洞](/zh-hans/4.0/topics/security/#cross-site-scripting) （因为 XSS 漏洞已经让攻击者做了 CSRF 漏洞允许的任何事情，甚至更糟）。

> **删除 Referer 头**
>
> 为了避免向第三方网站透露 referrer URL，你可能想在你的网站的 `<a>` 标签上 [禁用 referrer](https://www.w3.org/TR/referrer-policy/#referrer-policy-delivery) 。例如，你可以使用 `<meta name="referrer" content="no-referrer">` 标签或包含 `Referrer-Policy: no-referrer` 头。由于 CSRF 保护对 HTTPS 请求进行严格的 referer 检查，这些技术会在使用“不安全”方法的请求上导致 CSRF 失败。取而代之的是，使用诸如 `<a rel="noreferrer" ...>"` 这样的替代品来链接第三方网站。

## 缓存

如果 [`csrf_token`](/zh-hans/4.0/ref/templates/builtins/#std-templatetag-csrf_token) 模板标签被模板使用（或 `get_token` 函数被其他方式调用），`CsrfViewMiddleware` 将添加一个 cookie 和一个 `Vary: Cookie` 头到响应中。这意味着，如果按照指示使用，中间件将与缓存中间件很好地配合（`UpdateCacheMiddleware` 先于所有其他中间件）。

但是，如果你在单个视图上使用缓存装饰器，CSRF 中间件还不能设置 Vary 头或 CSRF cookie，响应将在没有任何一个的情况下被缓存。在这种情况下，在任何需要插入 CSRF 令牌的视图上，你应该先使用 [`django.views.decorators.csrf.csrf_protect()`](#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):
    ...
```

如果你使用的是基于类的视图，你可以参考 [装饰基于类的视图](/zh-hans/4.0/topics/class-based-views/intro/#id1)。

## 测试中

`CsrfViewMiddleware` 通常会成为测试视图功能的一大障碍，因为需要 CSRF 令牌，而 CSRF 令牌必须在每个 POST 请求中发送。 出于这个原因，Django 的 HTTP 测试客户端已经被修改为在请求中设置一个标志，放松中间件和 `csrf_protect` 装饰器，使它们不再拒绝请求。 在其他方面（如发送 cookie 等），它们的行为是一样的。

如果出于某种原因，你 *想* 让测试客户端执行 CSRF 检查，你可以创建一个执行 CSRF 检查的测试客户端实例：

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

## 限制

一个网站内的子域将能够在客户端设置整个域的 cookie。 通过设置 cookie 并使用相应的令牌，子域将能够规避 CSRF 保护。 避免这种情况的唯一方法是确保子域由受信任的用户控制（或者，至少无法设置 cookie）。 需要注意的是，即使没有 CSRF，也存在其他漏洞，比如会话固定，将子域交给不受信任的一方是个坏主意，而这些漏洞在目前的浏览器上是不容易修复的。

## 边缘案例

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

### 实用程序

下面的例子假设你使用的是基于函数的视图。如果你正在使用基于类的视图，你可以参考 [装饰基于类的视图](/zh-hans/4.0/topics/class-based-views/intro/#id1)。

#### `csrf_exempt(view)`

该装饰器标记着一个视图被免除了中间件所确保的保护。例如：

```
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def my_view(request):
    return HttpResponse('Hello world')
```

#### `requires_csrf_token(view)`

通常情况下，如果 `CsrfViewMiddleware.process_view` 或类似 `csrf_protect` 这样的等价物没有运行， [`csrf_token`](/zh-hans/4.0/ref/templates/builtins/#std-templatetag-csrf_token) 模板标签将无法工作。视图装饰器 `requires_csrf_token` 可以用来确保模板标签工作。这个装饰器的工作原理与 `csrf_protect` 类似，但绝不会拒绝接收到的请求。

举例：

```
from django.shortcuts import render
from django.views.decorators.csrf import requires_csrf_token

@requires_csrf_token
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)
```

#### `ensure_csrf_cookie(view)`

该装饰器强制视图发送 CSRF cookie。

### 情境

#### 仅仅是几个视图就应该禁用 CSRF 保护。

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

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

#### CsrfViewMiddleware.process\_view 没有使用

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

解决方法：使用 `requests_csrf_token()`。

#### 不受保护的视图需要 CSRF 令牌

可能有一些视图是不受保护的，已经被 `csrf_exempt` 豁免，但仍然需要包括 CSRF 令牌。

解决方法：使用 [`csrf_exempt()`](#django.views.decorators.csrf.csrf_exempt) 后面跟着 [`requires_csrf_token()`](#django.views.decorators.csrf.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`](/zh-hans/4.0/ref/templates/builtins/#std-templatetag-csrf_token) 的 HTML 表单，这将导致所需的 CSRF cookie 被发送。

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

## Contrib 和可重用的应用

因为开发者可以关闭 `CsrfViewMiddleware`，所以 contrib 应用程序中的所有相关视图都使用 `csrf_protect` 装饰器来确保这些应用程序对 CSRF 的安全性。 建议其他可重用应用的开发者，如果想要得到同样的保证，也在他们的视图上使用 `csrf_protect` 装饰器。

## 配置

一些配置可以用来控制Django 的 CSRF 行为：

- [`CSRF_COOKIE_AGE`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_AGE)
- [`CSRF_COOKIE_DOMAIN`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_DOMAIN)
- [`CSRF_COOKIE_HTTPONLY`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_HTTPONLY)
- [`CSRF_COOKIE_NAME`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_NAME)
- [`CSRF_COOKIE_PATH`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_PATH)
- [`CSRF_COOKIE_SAMESITE`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_SAMESITE)
- [`CSRF_COOKIE_SECURE`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_COOKIE_SECURE)
- [`CSRF_FAILURE_VIEW`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_FAILURE_VIEW)
- [`CSRF_HEADER_NAME`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_HEADER_NAME)
- [`CSRF_TRUSTED_ORIGINS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS)
- [`CSRF_USE_SESSIONS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_USE_SESSIONS)

## 常问问题

### 可以提交任意的 CSRF 令牌对（cookie 和 POST 数据）是漏洞吗？

不，这是设计好的。如果没有中间人攻击，攻击者就没有办法向受害者的浏览器发送 CSRF 令牌 cookie，所以成功的攻击需要通过 XSS 或类似的方式获得受害者浏览器的 cookie，在这种情况下，攻击者通常不需要 CSRF 攻击。

一些安全审计工具将此标记为问题，但如前所述，攻击者无法窃取用户浏览器的 CSRF cookie。使用 Firebug、Chrome 开发工具等“窃取”或修改 *自己的* 令牌并不是漏洞。

### Django 的 CSRF 保护默认不与会话关联，是不是有问题？

不，这是设计好的。不将 CSRF 保护与会话联系起来，就可以在诸如 *pastebin* 这样允许匿名用户提交的网站上使用保护，而这些用户并没有会话。

如果你希望在用户的会话中存储 CSRF 令牌，请使用 [`CSRF_USE_SESSIONS`](/zh-hans/4.0/ref/settings/#std-setting-CSRF_USE_SESSIONS) 设置。

### 为什么用户登录后会遇到 CSRF 验证失败？

出于安全考虑，每次用户登录时，CSRF 令牌都会轮换。任何在登录前生成表单的页面都会有一个旧的、无效的 CSRF 令牌，需要重新加载。如果用户在登录后使用后退按钮或在不同的浏览器标签页中登录，可能会发生这种情况。
