ユーザーがフォームと同じページを更新したときに、MySQL データベースにデータが 2 回入力されるのを防ぐために、他のプログラマーがどのような方法/防止策を使用しているのか疑問に思っていました。明らかにそれが起こり、これを止める良い方法が必要です。
16 に答える
私はこれを Web プログラミングの黄金律と呼んでいます。
POST リクエストに本文で応答しないでください。常に作業を行ってから Location: ヘッダーで応答して、更新されたページにリダイレクトし、ブラウザーが GET で要求するようにします。
このように、リフレッシュしても害はありません。
また、ここのコメントでの議論について。誤って [送信] ボタンをダブルクリックしてしまうなどの二重投稿を防ぐには、フォームの md5() をテキスト ファイルに保存し、新しいフォームの md5 と保存されているフォームを比較します。それらが等しい場合、二重投稿をしています。
フォームを処理してから、結果ページにリダイレクトします。リロードすると、結果ページのみが再表示されます。
イリヤの答えは正しいです。コメントに収まるよりも少しだけ追加したかっただけです。
再送信が危険な場合 (前に戻って再度送信する、結果ページをリロードする [Ilya のアドバイスを受けていない場合] など)、フォームが 1 回しか通過できないようにするために「ナンス」を使用します。
フォーム ページ:
<?php
@session_start(); // make sure there is a session
// store some random string/number
$_SESSION['nonce'] = $nonce = md5('salt'.microtime());
?>
// ... snip ...
<form ... >
<input type="hidden" name="nonce" value="<?php echo $nonce; ?>" />
</form>
処理中のページ:
<?php
if (!empty($_POST)) {
@session_start();
// check the nonce
if ($_SESSION['nonce'] != $_POST['nonce']) {
// some error condition
} else {
// clear the session nonce
$_SESSION['nonce'] = null;
}
// continue processing
フォームが一度送信されると、ユーザーが意図的に 2 回目に入力しない限り、再度送信することはできません。
明白なことを述べると(私はまだここでそれを見たことがない...):データを投稿するためにGETを使用しないでください。常にPOSTを使用してください。そうすれば、ユーザーがページを更新/再投稿しようとすると、少なくとも警告が表示されます(少なくとも Firefox ではそうですが、他のブラウザーでもそうだと思います)。
ところで、同じデータを 2 回保持する余裕がない場合は、一意のキー (フィールドの組み合わせである可能性があります) と次の MySQL ソリューションも検討する必要があります。
INSERT INTO ... ON DUPLICATE KEY UPDATE ...
最新のWebアプリが実装しているPOST/Redirect /GETパターンを確認することをお勧めします。http://en.wikipedia.org/wiki/Post/Redirect/Getを参照してください。
ユーザーを投稿ページから遠ざけて、更新ボタンと戻るボタンが無害になるようにすることについて既に述べた良い提案に加えて、データ ストレージを改善するもう 1 つの層は、UUIDをテーブルのキーとして使用し、アプリケーションでそれらを生成できるようにすることです。
これらは、Microsoft の世界では GUID としても知られており、PHP では、PHP の uniqid() を介して生成できます。これは 32 文字の 16 進数値であり、16 進数/バイナリ列形式で格納する必要がありますが、テーブルが頻繁に使用されない場合は、CHAR(32) が機能します。
フォームを非表示の入力として表示するときにこの ID を生成し、データベース列が主キーとしてマークされていることを確認してください。ユーザーが投稿ページに戻ることができた場合、キーを重複させることができないため、INSERT は失敗します。
これに対する追加のボーナスは、コードで UUID を生成すると、挿入を実行した後、生成されたキーを取得する無駄なクエリを使用する必要がないことです。これは、子アイテムを他のテーブルに INSERT する必要がある場合に便利です。
優れたプログラミングは、1 つの作業に依存するのではなく、作業を重ねることに基づいています。コーダーがインクリメンタル ID に依存することはよくあることですが、それらはテーブルを作成する最も怠惰な方法の 1 つです。
そこで Ilya に同意し、クライアントの JavaScript を使用して、クリックされた [送信] ボタンを無効にするか、モーダル ダイアログを表示して (css がここで役立ちます)、送信ボタンを複数回クリックしないようにする必要があることを追加します。
最後に、データベースにデータを 2 回入れたくない場合は、データを挿入する前にデータベースにもデータがあるかどうかを確認してください。重複レコードを許可するが、単一のソースからの迅速な繰り返し挿入を望まない場合は、時間/日付スタンプと IP アドレス フィールドを使用して、提出コードで時間ベースの「ロックアウト」を許可します。 IP が同じで、最後の送信時間が 5 分未満前である場合は、新しいレコードを挿入しないでください。
それがあなたにいくつかのアイデアを与えることを願っています。
私は通常、SQL UNIQUE インデックス制約に依存しています。 http://dev.mysql.com/doc/refman/5.0/en/constraint-primary-key.html
私の2セント:
- データを送信して検証した後、ユーザーを同じページにリダイレクトします
if(isset($_POST['submit'])
同様のケースに関するその他の有用な情報:
MySQL の LOCK TABLES を見てください
最初のクリック後に JavaScript 経由でボタンを無効にする
トークンを使用して、ページが再度処理されるのを防ぐことができます! このような手順は、多くの Web フレームワークで使用されています。
使うべきパターンは「シンクロナイザートークンパターン」!サービス指向のアプリケーションを使用している場合は、ステータスをデータベースに保存できます。
データは、JavaScript または非表示のフォーム フィールドを介して送信できます。
また、このようなものをすぐにサポートするライブラリを確認する必要があります! Grailsはそのようなものです!
参照: http://www.grails.org/1.1-Beta3+Release+Notes ...
<g:form useToken="true">
...
withForm {
// good request
}.invalidToken {
// bad request
}
..
POE (Post Once Exactly)は、独自のヘッダーを使用して二重送信をブロックするようにクライアントに警告することを目的とした HTTP パターンです...
GET /posts/new HTTP/1.1
POE: 1
...
...しかし、まだ仕様です。
http://www.mnot.net/drafts/draft-nottingham-http-poe-00.txt
上記のノンスは良い解決策だと思います。ただし、ノンスを個別のセッション変数として保存すると、クライアントが複数のタブから同時に投稿を実行しようとすると、いくつかのエラーが発生します。たぶん...
$_SESSION['nonces'][] = $nonce;
... と ...
if (in_array($_POST['nonce'], $_SESSION['nonces'])) {
... 複数のノンスを許可する (nonci? noncei?)。
まず第一に、最小化するには、データを挿入するためにフォーム ポストを実行する必要があるようにする必要があります。そうすれば、少なくとも、本当に再送信するかどうかを尋ねる素敵な小さな確認ダイアログが表示されます。
さらに複雑にするために、各フォームに非表示の 1 回限りのキーを配置し、そのキーを持つフォームが送信されると、同じキーを持つフォームを送信しようとするとエラーを表示することができます。このキーを作成するには、おそらく GUID などを使用する必要があります。