« 2008年01月 | トップ | 2008年06月 »

2008年02月06日

フォーム二重送信防止の考察  このエントリをはてなブックマークに登録 

Webアプリケーションなどにおいて、フォームの送信の際に、入力→確認→完了の手順をとることが多い。CRUD(Create Read Update Delete)のように、Input Confirm Completeで、ICCなんて個人的には呼んでいる。

それはさておき、確認画面から送信ボタンを押して、データベースに登録するなり、メール送信するなりをした場合に、そこでブラウザをリロードしたり、ブラウザの戻るボタンを押して再送信したり、ボタンを2回クリックしたりすると、フォームが二重に送信されてしまう。

これらの対処としては、トランザクショントークンを用いた対策方法が主流である。
確認フォームで、hiddenタグに乱数などで生成されたトークンを埋め込み、同時に同じキーをセッションに持つ。フォームが送信されたらトークンとセッションのキーが一致するか確認し、一致したら正常処理を行い、セッションのキーを破棄。一致しなかったら、エラーとみなし、処理を行わずにリダイレクトなど。

送信後にブラウザのバックなどで戻り、再送信した際には、セッションのキーは既に破棄されてしまっているので、一致することはなくエラーにしかならない。これで二重送信を防止。


とまぁ、これで問題はなさそうなのではあるんですが。

PHPの場合、セッションをスタートすると、デフォルトでヘッダーにno-cacheをセットする。
その状態で、ブラウザでバックし、確認画面などのhiddenタグを埋め込んでいるところに戻ると、no-cacheゆえにページが再読込されて、トークンキーの値が変わってしまい、セッションにもキーが再びセットされ、二重送信が行えるようになってしまう。

no-cacheにしない事がトランザクショントークンを使う場合の条件のようであるが、そのためには、セッションがスタートする前に、
session_cache_limiter('private_no_expire');
なんて書いておけくと良い。これでブラウザでバックしても再読込されないので、二重送信もしっかりと防ぐことが出来るようになる。

一般的なユーザーは、入力内容に間違いがあったと気がつくと、直感的に戻るボタンを押すことが多いと思うので、ブラウザのバック自体は行えたほうが良いが、最終的なフォーム送信後に戻れてしまうと、たとえ二重送信防止となっていて、二回目の送信は実際の処理が行われないとしても、ユーザーは訂正を行ったうえで、再送信を行ったのだと思うかも知れず、そうなってしまうとユーザーの意図と、システムの振る舞いに差異が生じるので、一度送信完了したなら、そのフォームには戻れないようにしたい。


ということで考えていること。

no-cacheが基本
フォーム生成時に、乱数等にてformidを生成。同時にセッションにformidを格納
formidパラメーターを付与したURLにリダイレクト。
ブラウザでバックした場合は、formidに紐づくセッション内容を元にフォームを復元
フォームの送信時にはformidに紐づくセッションを削除
紐づくセッションがない場合は、どこかに遷移
編集用のフォームは、初回の表示もセッションから呼び出し
CSRF対策のトークンキーも盛り込む

みたいな感じ。
こんな感じで、試しに実装してみようと思う。

最後の部分、何を書いているのか自分しか分からないかも。

2008-02-09追記
amazonで本を注文したついでにブラウザで戻って再送信してみた。
キャッシュが利くようになっていて、前に問題なく戻れるようになっている。
で、2重送信してみたところ、以下のメッセージが表示された。

確定後の注文を、ブラウザの「戻る」ボタンで注文手続きページに戻って変更することはできません。

ちゃんとメッセージを表示させれば、お客さんも混乱しないですね。

投稿者 田中@グリニッジ : 23:40 | コメント (0) | トラックバック