DjangoアプリでデータをPOSTする際、CSRFトークンは必須*1になります。
通常のリクエストだったら、条件反射的に {% csrf_token %}
をFormタグの中に入れるのに、Ajaxの時だけ、なぜか忘れて「動かない」と悩んでしまう・・・なんてことありませんか。
「CSRFトークン問題で時間を溶かしてしまった・・・」
なんてことを起こさないようにするため、備忘録を残します。
では、具体的に何をすれば良いか。
その答えは、公式のドキュメントにあります。
CSRFトークンを一緒に送信する方法
ヘッダー[X-CSRFToken]をセットするためのフック機能を追加する
DjangoドキュメントのAJAXの部分を引用
クロスサイトリクエストフォージェリ (CSRF) 対策 | Django documentation | Django
すべての POST リクエストで CSRF トークンを POST するデータに含めることを 覚えておかなければなりません。なので、別の方法が用意されています。それは、各 XMLHttpRequest に対して、X-CSRFToken という独自ヘッダーに CSRF トークンの 値を設定することです。多くの JavaScript のフレームワークはすべてのリクエストについて、ヘッダーを設定するようなフック機能を提供しているので、この操作は多くの 場合、簡単に行うことができます。 jQuery の場合、 ajaxSend イベントを以下の ように記述します
jQuery(document).ajaxSend(function(event, xhr, settings) { function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // 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; } function sameOrigin(url) { // url could be relative or scheme relative or absolute var host = document.location.host; // host + port var protocol = document.location.protocol; var sr_origin = '//' + host; var origin = protocol + sr_origin; // Allow absolute or scheme relative URLs to same origin return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || // or any other URL that isn't scheme relative or absolute i.e relative. !(/^(\/\/|http:|https:).*/.test(url)); } function safeMethod(method) { return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } if (!safeMethod(settings.type) && sameOrigin(settings.url)) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } });
カスタムヘッダー「X-CSRFToken」に対して、CSRFトークンを設定するスクリプトを組み込んでおき、Ajaxを実行することによって、CSRF検証に失敗したというエラーを回避することができます。
スクリプトを導入するにあたっては、下記のような方法が良いかもしれません。
- 上記、ajaxSendスクリプトが記載されたDjangoテンプレートファイルを別途用意しておき、Ajaxを実行するテンプレート画面で
include
する- メリット:Ajaxが必要な画面のみに、X-CSRFTokenヘッダを付与する処理が実行される
- デメリット:複数画面でAjaxを実行する場合、その都度テンプレートをincludeする必要がある
- 基本となるベーステンプレートを用意し、その中にajaxSendスクリプトを記述。他の画面を作成する際にはベーステンプレートを継承
extend
する- メリット:ベーステンプレートさえ継承していれば、X-CSRFTokenを含めてリクエストを送信してくれるので、CSRF検証エラーを意識しなくて良い
- デメリット:Ajaxを実行しない画面でも、X-CSRFTokenを含めるスクリプトが読み込まれる
FullCalendarを利用した例
<!-- Djangoテンプレートの構造は割愛 --> <link href="{% static 'css/fullcalendar.min.css' %}" rel="stylesheet"> <link href="{% static 'css/fullcalendar.print.css' %}" rel="stylesheet" media="print"> <script src="{% static 'js/fullcalendar.min.js' %}"></script> <script type="text/javascript"> // X-CSRFTokenにセットするフックコード(引用元のコード)を書いていることを前提 // なお先頭は$(document).ajaxSend(function(event, xhr, settings) {に変更した $(document).ready(function() { $('#calendar').fullCalendar({ header: { left: 'prev, next, today', center: 'title', right: 'month, basicWeek, basicDay' }, defaultDate: '2015-03-18', editable: true, eventLimit: true, events: { url: '{% url 'api:any_view' %}', type: 'POST', error: function() { console.log("error"); }, success: function() { console.log("ok"); } } }); }); </script> <div id="calendar" class="fc fc-ltr fc-unthemed"></div>
views.py
# coding: utf-8 def any_view(request): user = request.user start = request.POST["start"] # FullCalendarのパラメータ end = request.POST["end"] # FullCalendarのパラメータ any_list = Any.objects.filter(user__username=user.username, any_ymd__range=(start, end) ) #... jsonを返却 [{'title':'any_event', 'start': '2015-03-20'}...]
urls.py(project)
# 追記 url(r'^api/', include('api.urls', namespace='api')),
urls.py(app)
from django.conf.urls import patterns, url from api import any urlpatterns = patterns('', url('^any/$', views.any_view, name='any_view'), )
結果のコード
(fullcalendarに渡すjsonレスポンスの部分は割愛したため、上記コードをそのまま書いてもこの結果にはならないのですが、適切にAjax送信できたことがわかります)
それでは、皆さんも楽しいDjangoコーディングを!
おすすめのPython本
- 作者: 株式会社ビープラウド
- 出版社/メーカー: 秀和システム
- 発売日: 2015/05/21
- メディア: Kindle版
- この商品を含むブログを見る
- 作者: Mark Lutz,夏目大
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/02/26
- メディア: 大型本
- 購入: 12人 クリック: 423回
- この商品を含むブログ (133件) を見る
*1:ミドルウェアを有効にしているか、アノテーションをつけていない限り