dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[django]クラスベースビュー(Class-based views)のCSRF対策を無効にする

DjangoミドルウェアCSRFミドルウェア(django.middleware.csrf.CsrfViewMiddleware)を指定していると、
csrf_tokenを渡していないPOSTメソッドは403エラーになってしまいます。

ただし、外部からのアクセスを可能にしたい場合など、特定のViewのメソッドに対しては
このCSRF対策を無効にしたい場合がありますよね。
その方法を調べたのでメモ。

はじめに

403エラーが発生する例を見てみます。

settings.py(一部)

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

template.html

<html>
<head>
</head>
<body>
    <form action="/view1/" method="post">
        <!-- {% csrf_token %} -->
        <div><input type="submit" value="Class-based View1(FAIL)"></div>
    </form>
</body>
</html>

urls.py

from django.conf.urls import patterns
from django.views.generic import TemplateView
from .views import CsrfView1

urlpatterns = patterns('',
    (r'^view1/', CsrfView1.as_view()),
    (r'^$', TemplateView.as_view(template_name='template.html')),
)

views.py

from django.http import HttpResponse
from django.views.generic.base import View


class CsrfView1(View):

    def post(self, request):
        return HttpResponse('ok')

この例では、http://localhost:8080にアクセスし

f:id:dackdive:20140923224541p:plain

ボタンをクリックすると、

f:id:dackdive:20140923224601p:plain

というエラーが発生します。

これを避けるためには<form>タグ内でcsrf_tokenテンプレートタグを使用します。
template.htmlコメントアウトしている部分のコメントを外すと、エラーは表示されなくなります。)

CSRF対策を無効にする方法

CSRF対策のチェックを無効にするには、@csrf_exemptというデコレータを使用します。

ここで、公式ドキュメント(英語です)によれば

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

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

でいいと書かれていますが、対象が今回のようなクラスベースビュー(Class-based views)の場合は注意が必要です。

具体的には、以下のような書き方をします。

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import View
from django.utils.decorators import method_decorator


class CsrfView2(View):

    def post(self, request):
        return HttpResponse('ok')

    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(CsrfView2, self).dispatch(*args, **kwargs)

ポイントは

  • csrf_exemptデコレータはdispatchメソッドにつける
  • csrf_exemptmethod_decoratorメソッドでラップする

補足

クラスベースビューの場合にどうしてこのような書き方になるのか?という部分について
公式ドキュメントには次のように記載されています。

(日本語URL) http://docs.djangoproject.jp/en/latest/topics/class-based-views.html

(英語URL) https://docs.djangoproject.com/en/1.5/topics/class-based-views/intro/#decorating-the-class

以下、ドキュメントより引用。

 To decorate every instance of a class-based view, you need to decorate the class definition itself. To do this you apply the decorator to the dispatch() method of the class.

 A method on a class isn’t quite the same as a standalone function, so you can’t just apply a function decorator to the method – you need to transform it into a method decorator first. The method_decorator decorator transforms a function decorator into a method decorator so that it can be used on an instance method.

日本語版では

 クラスベースビューの全インスタンスをデコレートするには、クラスの定義そのものをデコレートします。クラスの dispatch() メソッドにデコレータを適用することでできます。

 クラスメソッドは単なる関数とは違います。メソッドに関数デコレータを適用することはできません。関数デコレータをメソッドデコレータに変換する必要があります。 method_decorator デコレータは関数デコレータをメソッドデコレータに変換するので、これをインスタンスメソッドで使えます。

一段落目はクラスベースビューのデコレータはdispatch()メソッドに適用してくださいねーということが書いてますね。

二段落目は、クラスに定義している "メソッド(method)" と一般的な "関数(function)" は同じものではないので、適用したいデコレータの変換が必要だと述べています。

ただし、検証したところXXXView.as_view()という形で使っている限りでは、@method_decoratorはなくてもエラーにはなりませんでした。

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import View
from django.utils.decorators import method_decorator


class CsrfView3(View):

    def post(self, request):
        return HttpResponse('ok')

    # これでもうまくいく
    @csrf_exempt
    def dispatch(self, *args, **kwargs):
        return super(CsrfView3, self).dispatch(*args, **kwargs)

リファレンス

http://blog.joshcrompton.com/2012/09/how-to-turn-off-csrf-protection-for.html

http://www4370ue.sakura.ne.jp/crimson/2014/04/django%E3%81%AEcsrf%E9%98%B2%E5%BE%A1%E3%82%92%E5%88%B6%E5%BE%A1%E3%81%99%E3%82%8B/

以下は日本語Django公式リファレンス

http://docs.djangoproject.jp/en/latest/ref/contrib/csrf.html

http://docs.djangoproject.jp/en/latest/topics/class-based-views.html