django で POST データを受け取ると 403 エラーが出る

同じ URL は GET では正常にアクセスできるのに、POST でデータを送信すると 403 エラーが返る場合、たいていは Django の CSRF 検証に通っていないことが原因です。

Django ではデフォルトで CSRF 保護が有効になっています。プロジェクトで次の設定が有効になっている場合:

'django.middleware.csrf.CsrfViewMiddleware',

POST リクエストには正しい CSRF token を含める必要があります。そうでない場合、Django はリクエストを拒否し、403 を返します。

推奨方法:CSRF token を送信する

通常のフォームであれば、テンプレート内の <form> タグの中に次を追加します:

<form method="post">
    {% csrf_token %}
    <!-- form fields -->
    <button type="submit">Submit</button>
</form>

これにより、Django はページ内に hidden フィールドを生成します。POST 時にその値も一緒に送信されるため、CSRF 検証を通過できます。

Ajax リクエストの場合は、CSRF token をリクエストヘッダーに入れる必要があります。一般的には cookie から csrftoken を読み取り、POST リクエストと一緒に送信します:

fetch('/your-url/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRFToken': csrftoken
  },
  body: JSON.stringify({ name: 'value' })
})

方法 1:CSRF ミドルウェアを無効にする

settings.py で次の行をコメントアウトできます:

'django.middleware.csrf.CsrfViewMiddleware',

こうすると、POST リクエストが CSRF 検証の失敗によって 403 を返すことはなくなります。

ただし、これはプロジェクト全体の CSRF 保護を無効にするため、本番環境では推奨されません。ローカルテストや、用途が非常に明確な内部インターフェースの場合を除き、CSRF token を使う方法を優先すべきです。

方法 2:単一のビューだけ CSRF 検証を無効にする

特定のインターフェースだけ CSRF 検証が不要な場合は、view の request handler の前に @csrf_exempt を付けます:

# views.py

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

@csrf_exempt
def index(request):
    return HttpResponse('ok')

この方法は、デコレーターを付けたビューにだけ影響するため、ミドルウェア全体を無効にするより範囲が小さくなります。ただし、そのインターフェースは CSRF 保護をスキップすることになるため、本当に必要な場合にのみ使うべきです。たとえば、サードパーティのコールバック用インターフェース、内部サービス用インターフェース、または別の認証・署名メカニズムを備えた API などです。

Leave a Reply