二重サブミット・二重送信とは?
二重サブミットとは同じリクエストが複数送られてしまうことです。
POSTリクエストを受け付けた際に行う処理が複数回実行されることによって、想定していない処理が行われる恐れがあります。
ブラウザの再読込ボタン等によっても発生しうるため、何らかの対策をするのが望ましいです。
参考
Djangoに二重サブミット対策の機能はある?
根気よく探しましたがなさそうだったので、簡単に自分で作りました。
ボタンの非活性化等クライアントサイドではなく、サーバーサイドでの二重サブミット判定です。
def set_submit_token(request):
submit_token = str(uuid.uuid4())
request.session['submit_token'] = submit_token
return submit_token
def exists_submit_token(request):
token_in_request = request.POST.get('submit_token')
token_in_session = request.session.POP('submit_token', '')
if not token_in_request:
return False
if not token_in_session:
return False
return token_in_request == token_in_session
使い方
サンプルでは、以下の流れとなっています。
- (サーバ -> クライアント) index画面アクセス時にsubmit_tokenをセッション内に保持し、同時にクライアント渡す
- (クライアント -> サーバ) index画面でのsubmitボタン押下時に、リクエストにsubmit_tokenを含ませる
- (サーバ) submit_tokenがセッションの中にあるか確認する
exists_submit_token()では一度使ったsubmit_tokenはpopによりセッションから破棄するため、同じリクエストが複数回送られた場合は、error.htmlを返却する処理を行います。
views.py
def index(request):
submit_token = set_submit_token(request)
return render(request, 'todo/index.html', {"submit_token": submit_token})
def post(request):
if not exists_submit_token(request):
return render(request, 'todo/error.html', {})
else:
return render(request, 'todo/complete.html', {})
index.html
<form action="{% url 'todo:post' %}" method="post">
{% csrf_token %}
{{ submit_token }}
<input type="hidden" name="submit_token" value="{{ submit_token }}" />
<input type="submit" value="Submit" />
</form>
最後に
もっとシンプルでわかりやすい方法があればそれを使いたいのですが、上記の実装で一旦妥協してしまいました。
また、Struts2におけるSessionTokenのように、同じリクエストの二回目以降も、処理はしないけど全く同じ画面を返却すると言うのも出来るようにしたいですね…。
良さげなライブラリや、もっとこうしたほうが良いとか、自分はこうやったがあれば、ぜひコメントでご教示ください。