web-dev-qa-db-ja.com

データベースへのデータの挿入を2回停止します

PHPはまったく新しいのですが、ユーザーがフォームと同じページで更新したときに、他のプログラマーがMySQLデータベースにデータが2回入力されるのを防ぐためにどのような方法/防止策を使用するのか疑問に思いました。明らかにそれは起こります、そして私はこれを止める良い方法が必要です。

ありがとう、ベン

25
Ben McRae

私はこれをWebプログラミングの黄金律と呼んでいます。

POSTリクエストに本文で応答しないでください。常に作業を行い、次にLocation:ヘッダーで応答して、更新されたページにリダイレクトし、ブラウザーがGETで要求するようにします。

このように、リフレッシュしても害はありません。

また、ここでの議論についてはコメントで。たとえば、誤って[送信]ボタンをダブルクリックすることによる二重投稿から保護するには、フォームのmd5()をテキストファイルに保存し、新しいフォームのmd5を保存されているものと比較します。それらが等しい場合、あなたは二重のポストを持っています。

40
Ilya Birman

フォームを処理してから、結果ページにリダイレクトします。再読み込みすると、結果ページのみが再表示されます。

7
chaos

イリヤの答えは正しいです、私はコメントに収まるより少し多くを追加したかっただけです:

再送信が危険な場合(戻って再送信する、結果ページを再読み込みする[Ilyaのアドバイスを受けていない場合]など)、フォームが1回しか通過できないように「nonce」を使用します。

フォームページ:

<?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回入力しない限り、フォームを再度送信することはできません。

5
James Socol

明白なことを述べると(私はまだここでそれを見ていません...):データを投稿するためにGETを使用しないでください。常にPOSTを使用してください。そうすれば、ユーザーがページを更新/再投稿しようとすると、少なくとも警告が表示されます。 (少なくともFirefoxでは、他のブラウザでもそうだと思います)。

ちなみに、同じデータを2回取得する余裕がない場合は、一意のキー(フィールドの組み合わせでもかまいません)を使用したMySQLソリューションを検討する必要があります。

    INSERT INTO ... ON DUPLICATE KEY UPDATE ...
4
jeroen

私はそこでIlyaに同意し、クライアントjavascriptを使用して、クリックされたら[送信]ボタンを無効にするか、送信ボタンが複数回クリックされないようにモーダルダイアログ(cssがここで役立ちます)を表示する必要があることを追加します。

最後に、データベースにデータを2回入れたくない場合は、データを挿入する前に、データベースでデータを確認してください。重複レコードを許可しているが、単一のソースからの迅速な繰り返し挿入を望まない場合は、時間/日付スタンプとIPアドレスフィールドを使用して、送信コードで時間ベースの「ロックアウト」を許可します。 IPは同じで、最後の送信時間は5分未満でした。その場合、新しいレコードを挿入しないでください。

それがあなたにいくつかのアイデアを与えることを願っています。

2
Lazarus

更新と戻るボタンが無害になるようにユーザーを投稿ページから遠ざけることについてすでに述べた良い提案に加えて、データストレージを改善する別のレイヤーは [〜#〜] uuid [〜#〜 ] sをテーブルのキーとして使用し、アプリケーションに生成させます。

これらは、Microsoftの世界ではGUIDとも呼ばれ、PHP PHPではuniqid()を介して生成できます。これは32文字の16進値であり、16進/バイナリ列に格納する必要があります。形式ですが、テーブルが頻繁に使用されない場合は、CHAR(32)が機能します。

フォームを非表示の入力として表示するときにこのIDを生成し、データベース列が主キーとしてマークされていることを確認してください。これで、ユーザーが投稿ページに戻ることができた場合、重複するキーを使用できないため、INSERTは失敗します。

これに対する追加のボーナスは、コードでUUIDを生成する場合、挿入を実行した後、生成されたキーを取得するために無駄なクエリを使用する必要がないことです。これは、子アイテムを他のテーブルに挿入する必要がある場合に便利です。

優れたプログラミングは、1つの作業に依存するのではなく、作業を階層化することに基づいています。コーダーがインクリメンタルIDに依存することは非常に一般的ですが、それらはテーブルを構築するための最も怠惰な方法の1つです。

2
TravisO

最新のWebアプリが実装しているPOST/Redirect/GETパターンを確認することをお勧めします。 http://en.wikipedia.org/wiki/Post/Redirect/Get を参照してください。

2
jmoz

たとえば、showAddProductForm()メソッド内のhtmlにuniqid変数を渡し、$ _ SESSIONに同じuniqidを渡す必要があります。

public function showAddProductForm()
{
    $uniId = uniqid();
    $_SESSION['token'][$uniId] = '1';
    $fields['token'] = 'token['.$uniId.']';

    $this->fileName = 'product.add.form.php';
    $this->template($fields);
    die();
}

次に、showAddProductFormメソッドからHTMLに渡されたuniqidの値を使用して、フォーム内のHTMLコードに非表示の入力を配置する必要があります。

<input type="hidden" name="<?=$list['token']?>" value="1">

送信イベントの直後に、addProduct()メソッドの最初でそれを分析します。トークンが$ _SESSIONに存在し、その配列内に等しい値がある場合、これは新しい要求です。彼を適切なページにリダイレクトし、トークンの設定を解除して、挿入を続行します。それ以外の場合は、リロードページまたは繰り返しのリクエストから、addProdeuctページにリダイレクトします

public function addProducts($fields)
{
    $token_list = array_keys($fields['token']);
    $token = $token_list['0'];
    if (isset($_SESSION['token'][$token]) and $_SESSION['token'][$token] == '1') {
        unset($_SESSION['token'][$token]);
    } else {
        $this->addAnnounceForm($fields, '');
    }
}

トークンの配列がなぜ単一の変数ではないのかと疑問に思うかもしれません。管理パネルでは、ユーザーがマルチタブを開いてマルチタブに挿入するため、マルチタブを使用するとこのアルゴリズムは失敗します。

この方法を理解してくれた人に感謝します malekloo

1
VeRJiL

私は通常、SQLUNIQUEインデックス制約に依存しています。 http://dev.mysql.com/doc/refman/5.0/en/constraint-primary-key.html

1
Carlo

まず第一に、最小化するには、データを挿入するためにフォーム投稿を行う必要があるようにする必要があります。そうすれば、少なくとも、本当に再送信するかどうかを尋ねる素敵な小さな確認ダイアログが表示されます。

それよりも複雑にするために、各フォームに非表示の1回限りの使用キーを配置し、そのキーを使用してフォームを送信すると、同じキーを使用してフォームを送信しようとするとエラーが表示されます。このキーを作成するには、GUIDなどを使用することをお勧めします。

0
Kibbee

[〜#〜] 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?)を許可します。

0
KevBurnsJr

ランダムな文字列(たとえば、md5(uniqid())によって生成される)を使用して非表示フィールドを追加し、その文字列のフィールドをデータベースに作成して、一意にします。

0

私の2セント:

  • データを送信した後、ユーザーを同じページにリダイレクトし、if(isset($_POST['submit'])を確認します

同様のケースに関するその他の有用な情報:

  • MySQLのLOCK TABLES を見てください

  • 最初のクリック後にJavaScriptを介してボタンを無効にする

0
vyger

ページの更新時にレコードが重複して挿入されないようにする最善の方法は、ボタンをクリックしてデータベースにレコードを挿入した後、次の行を追加することです。

Response.Write("<script>location.href='yourpage.aspx'</script>");
0
kumar

トークンを使用して、ページが再度処理されないようにすることができます。このような手順は、多くのWebフレームワークで使用されています。

使用すべきパターンは「シンクロナイザートークンパターン」です!サービス指向のアプリケーションがある場合は、ステータスをデータベースに保存できます。

データは、JavaScriptまたは非表示のフォームフィールドを介して送信できます。

また、このようなものをすぐにサポートするライブラリも確認する必要があります。 Grailsはそのようなものです!

参照: http://www.grails.org/1.1-Beta3+Release+Notes .。

<g:form useToken="true">

.。

withForm {
   // good request
}.invalidToken {
   // bad request
}

..

0
Martin K.

二重提出を防ぐために、できれば同時にクロスサイトリクエストフォージェリから保護するために、フォームに何かを含めてみてください。私が呼んでいるformkeyを使用することをお勧めします。これは、フォームの送信を一意に識別して結び付ける1回限りのフィールドです。個々のアドレスの個々のユーザーに。コンセプトは他の名前でも使われていますが、私がリンクした短いメモはそれを十分に説明しています。

0
Keith Gaughan