DjangoBrothers BLOG ✍️

2020/10/28

このエントリーをはてなブックマークに追加
セキュリティ CSRF

【Django】 csrf_tokenの仕組みとCSRF無効化・画面カスタマイズする方法

「そもそもCSRFって何なの?」という方はこちらの記事を読んでみてください。

csrf_tokenの役割

Djangoでは、デフォルトでCSRFの検証を行ってくれます。

settings.pyに記載されている'django.middleware.csrf.CsrfViewMiddleware'によってCSRF検証機能が設定されています。

POSTメソッドのフォームには、csrf_tokenタグを入れればOKです。

タグを追加

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

こうすることで、formタグの中にname=csrfmiddlewaretokenのinputタグが自動的に生成されます。

このタグが生成される

<input type="hidden" name="csrfmiddlewaretoken" value="JjAP5uW656Xn9WMLZ3chqT1haPekrHDQil0KOgNpR0WVojLhN4T4Q9N3B2te6CqW">

inputタグのtypeはhiddenなので画面には表示されません。(ブラウザの検証機能を使うとタグが生成されていることを確認できます。)

valueには、サーバー側で発行されたトークンが設定されます。

このフォームが送信される時には、トークン情報も一緒に送信されることになります。

リクエストを受け取ったViewは、送られてきたトークンが正しいものか(サーバーが発行したトークンと一致するかどうか)を検証します。 検証の結果、正常なリクエストだと認められれば処理を実行します。正常なリクエストだと認められなければ403エラー画面を表示します。

もし{% csrf_token %}タグをつけ忘れた場合は、トークンがサーバーに送られないので403エラーが発生し、アクセス禁止(403) CSRF検証に失敗したため、リクエストは中断されました。と画面に表示されます。

注意点として、外部のURLに対してPOSTするフォームにはこの{% csrf_token %}タグを使ってはいけません。CSRFトークンが外部にもれることになり、セキュリティ上よくないからです。

CSRF検証を無効化する例

場合によっては、CSRF検証を無効化したいことも考えられます。

例えば、外部サイトからのPOSTを受けつける場面などでは、CSRF検証を免除しないとリクエストを正しく受け取れないことなどが考えられると思います。こういう場面では、CSRF検証を無効化します。(ただし、CSRF検証を免除する代わりに、他の方法で正しい(想定したサイトの想定したユーザーから受け取った)リクエストかどうかを検証する必要があります。)

settings.py'django.middleware.csrf.CsrfViewMiddleware'がデフォルトで設定されているので、これを削除してしまえば、CSRF検証機能は一括で無効化できます。

もし個別のViewごとにCSRF検証機能をつけたい場合は対象のViewに@csrf_protectを設定すれば対策できます。

@csrf_protectをつけたViewはCSRF検証される

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)

ただし、@csrf_protectつけ忘れてしまったViewはCSRFが対策できていないことになるので、'django.middleware.csrf.CsrfViewMiddleware'を削除することでCSRFを一括で無効化するやり方は非推奨となっています。

'django.middleware.csrf.CsrfViewMiddleware'は残しておき、以下のようにCSRF検証を無効化したい関数に@csrf_exemptとするやり方が良いでしょう。

@csrf_exemptをつけたViewはCSRF検証が無効化される

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

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

403エラー画面(CSRF検証失敗画面)のカスタマイズ

デフォルトの403エラー画面は以下のようになります。

カスタマイズしたい場合は、403_csrf.htmlという名前のHTMLファイルを作ることで、エラー発生時にはそのファイルが画面に表示されます。 設定方法は404のページと同じなのでこちらの記事を参考にしてみてください。

HTMLを表示するだけではなくて、関数で何かしらの処理をしたい場合は以下のように設定することもできます。

settings.pyにCSRF_FAILURE_VIEWを追加

CSRF_FAILURE_VIEW='app.views.csrf_failure'

上記の設定で、app/views.pyにあるcsrf_failure関数が実行されます。

403エラーが発生したときの処理

def csrf_failure(request, reason=""):

    # 何かしらの処理

    return HttpResponseForbidden('<h1>403 Forbidden</h1>', content_type='text/html')

関数の返却値としてはHttpResponseForbiddenを使うと良いでしょう。