如何使用 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 框架都提供允許在每個請求上設定標頭的 Hook。

首先,你必須取得 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 }}

使用裝飾器方法

你可以在需要保護的特定視圖上使用 csrf_protect() 裝飾器,而不是全面性地新增 CsrfViewMiddleware 作為保護,其功能完全相同。它必須 **同時** 用於在輸出中插入 CSRF 權杖的視圖,以及接受 POST 表單資料的視圖。(這些通常是同一個視圖函式,但不總是如此)。

**不建議** 單獨使用裝飾器,因為如果你忘記使用它,就會出現安全漏洞。「雙重保護」策略,也就是同時使用兩者是可以的,而且只會產生極少的額外負擔。

處理遭拒絕的請求

預設情況下,如果傳入的請求未能通過 CsrfViewMiddleware 執行的檢查,則會向使用者傳送「403 Forbidden」回應。通常只有在發生真正的跨站請求偽造,或是因為程式設計錯誤而未在 POST 表單中包含 CSRF 權杖時才會看到此狀況。

但是,錯誤頁面不是很友善,因此你可能會想要提供自己的視圖來處理這種情況。若要這麼做,請設定 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 保護

由於每個 POST 請求都必須傳送 CSRF 令牌,因此 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 請求,並且該頁面沒有 HTML 表單,其中包含 csrf_token,這會導致傳送所需的 CSRF cookie。

解決方案:在傳送頁面的視圖上使用 ensure_csrf_cookie()

可重複使用應用程式中的 CSRF 保護

由於開發人員可以關閉 CsrfViewMiddleware,因此 contrib 應用程式中的所有相關視圖都使用 csrf_protect 裝飾器,以確保這些應用程式的安全性不受 CSRF 影響。建議其他想要相同保證的可重複使用應用程式的開發人員也在其視圖上使用 csrf_protect 裝飾器。

返回頂部