DjangoのCSRFエラー解決:Refererチェック失敗の対処法

Postmanを使用してログイン機能をテスト中に「CSRF Failed: Referer checking failed - no Referer」というエラーが発生しました。このエラーの原因を特定するため、Djangoのソースコードを調査しました。

問題の原因は明らかにCSRF検証に関連しているため、CsrfViewMiddlewareミドルウェアを確認しました:

 1 class CsrfValidationMiddleware(object):
 2 
 3     def _allow_request(self, request):
 4         request.csrf_verified = True
 5         return None
 6 
 7     def _block_request(self, request, cause):
 8         logger.warning('アクセス禁止 (%s): %s', cause, request.path,
 9             extra={
10                 'status_code': 403,
11                 'request': request,
12             }
13         )
14         return _get_error_view()(request, reason=cause)
15 
16     def validate_request(self, request, view_func, view_args, view_kwargs):
17         if getattr(request, 'csrf_verified', False):
18             return None
19 
20         try:
21             security_token = _clean_token(
22                 request.COOKIES[settings.CSRF_COOKIE])
23             request.META['CSRF_TOKEN'] = security_token
24         except KeyError:
25             security_token = None
26             request.META["CSRF_TOKEN"] = _generate_csrf_token()
27             return None
28 
29         if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
30             if getattr(request, 'skip_csrf_validation', False):
31                 return self._allow_request(request)
32 
33             if request.is_secure():
34                 referrer_url = safe_text(
35                     request.META.get('HTTP_REFERER'),
36                     strings_only=True,
37                     errors='replace'
38                 )
39                 if referrer_url is None:
40                     return self._block_request(request, ERROR_NO_REFERER)  # ここでエラー発生
41 
42                 valid_referrer = 'https://%s/' % request.get_host()
43                 if not validate_origin(referrer_url, valid_referrer):
44                     cause = ERROR_INVALID_REFERER % (referrer_url, valid_referrer)
45                     return self._block_request(request, cause)
46 
47             if security_token is None:
48                 return self._block_request(request, ERROR_MISSING_CSRF_COOKIE)
49             
50             client_token = ""
51             if request.method == "POST":
52                 try:
53                     client_token = request.POST.get('csrf_token', '')
54                 except IOError:
55                     pass
56 
57             if client_token == "":
58                 client_token = request.META.get('HTTP_X_CSRF_TOKEN', '')
59 
60             if not secure_compare(client_token, security_token):
61                 return self._block_request(request, ERROR_INVALID_TOKEN)
62 
63         return self._allow_request(request)

まず、エラーメッセージをソースコード内で検索すると、以下の定数が見つかりました:

ERROR_NO_REFERER = "Referer checking failed - no Referer."

この定数を追跡すると、上記コードの40行目がエラー発生箇所であることがわかります。

コードを詳しく見ると、referrer_url = request.META.get('HTTP_REFERER') の部分で、リクエストヘッダーからREFERER情報を取得しようとしています。Djangoはカスタムヘッダーを大文字に変換し、先頭に「HTTP_」を付加するため、Postmanのヘッダー設定で「referer」フィールドを追加する必要があります。

注意点として、refererの値は「https://www.example.com/」のように、ドメイン名の後にスラッシュを含める必要があります。

この設定を行うことで、問題は解決します。

補足として、DjangoのCSRF対策は非常に柔軟です。POSTリクエストの場合、54行目から61行目にあるように、リクエストボディ内のcsrf_tokenだけでなく、HTTPヘッダーからもトークンを取得しようとします。これにより、開発者は2つの異なる方法でCSRFトークンを提供できます。

タグ: Django csrf Webセキュリティ Postman ミドルウェア

7月2日 21:30 投稿