Django の csrf_token について

Djangocsrf_token について、Twitter のフォロワーさんが困ってました。
以前同じように困った事があるから助け舟を出したのですが、
気になってググってみると日本語情報があまりないような?
なので blog にまとめておきます。


ちなみに、公式ドキュメントはこちら
Cross Site Request Forgery protection | Django documentation | Django


csrf_token って?

クロスサイトリクエストフォージェリ(CSRF) を防ぐためのモノです。
Django 1.2 から追加されました。
詳細については Ian さんトコで。
Django 1.2 の変更のまとめ - Ian Lewis

何に困るの?

form で method="POST" 使うと以下のエラーページが表示される。

Forbidden (403)

CSRF verification failed. Request aborted.

原因

settings.py で CsrfViewMiddleware を追加している(コメントアウトを外している)のに
csrf_token が form から渡されてきていないため。


settings.py (抜粋)

MIDDLEWARE_CLASSES = (
  'django.middleware.csrf.CsrfViewMiddleware',
)

対処法

HTML 側は Form に csft_token csrf_token を含める。


index.html

<form method="POST" action="{% url search %}" name="SEARCH">
  {% csrf_token %}
  <input type="text" name="keyword" />
  <input type="submit" value="検索" />
</form>


view 側は RequestContext 使う。
Context だと宣言した分のパラメータしか渡してくれないため、
Django が作った CSRF トークンが渡せない。


views.py

from django.template import RequestContext
from django.shortcuts import render_to_response


def root(request):
  """
  index
  """
  ctxt = RequestContext(request, {})
  return render_to_response("index.html", ctxt)


def search(request):
  """
  search
  """
  if "keyword" in request.POST:
    keyword = request.POST["keyword"]
  else:
    keyword = ""
  ctxt = RequestContext(request, {
    "keyword": keyword
  })
  return render_to_response("search.html", ctxt)

そんな感じ。


余談

django.middleware.csrf.CsrfViewMiddleware を使わない場合。
特定の箇所のみ CSRF 対応したい時にでも?
ただし、必要なのに忘れた場合セキュリティホールになるので注意


HTML 側は {% csrf_token %} 必須。


index.html

<form method="POST" action="{% url search %}" name="SEARCH">
  {% csrf_token %}
  <input type="text" name="keyword" />
  <input type="submit" value="検索" />
</form>


view 側は 表示(root) と 取得(search) 両方に @csrf_protect が必要。


views.py

# -*- coding: utf-8 -*-
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.views.decorators.csrf import csrf_protect


@csrf_protect
def root(request):
  """
  index
  """
  ctxt = RequestContext(request, {})
  return render_to_response("index.html", ctxt)

@csrf_protect
def search(request):
  if "keyword" in request.POST:
    keyword = request.POST["keyword"]
  else:
    keyword = ""
  ctxt = RequestContext(request, {
    "keyword": keyword
  })
  return render_to_response("search.html", ctxt)


あとでソースまとめて UP するかな…。

追記:UPした。
https://bitbucket.org/blaue_fuchs/csrf_sample/src