首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

防止表單重複提交:前端攔截 + 後端 Token 雙重保障

     編輯:PHP 相關知識

問題場景

  • 使用者連點提交按鈕。
  • 網路延遲導致使用者以為失敗而重整/重送。
  • 惡意腳本重放請求。

方案一:前端攔截(體驗優化,非安全防線)

按鈕禁用法

<form id="myForm" action="/submit" method="post">
    <input type="text" name="content" required>
    <button type="submit" id="btnSubmit">送出</button>
</form>
<script>
document.getElementById('myForm').addEventListener('submit', function(e) {
    const btn = document.getElementById('btnSubmit');
    btn.disabled = true;
    btn.textContent = '送出中...';
    // 可選:設定逾時恢復,防止 JS 錯誤導致永久禁用
    setTimeout(() => { btn.disabled = false; btn.textContent = '送出'; }, 10000);
});
</script>

旗標變數法

<script>
let submitted = false;
document.getElementById('myForm').addEventListener('submit', function(e) {
    if (submitted) {
        e.preventDefault();
        alert('請勿重複提交');
        return;
    }
    submitted = true;
});
</script>

方案二:後端 Session Token(核心防禦)

原理:表單產生時產生唯一 Token 存入 Session 並置於隱藏欄位。提交時比對並銷毀 Token,後續重複提交將失效。

表單頁 (form.php)

<?php
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['form_token'] = $token;
?>
<form action="submit.php" method="post">
    <input type="hidden" name="token" value="<?php echo $token; ?>">
    <textarea name="content" required></textarea>
    <button type="submit">發表</button>
</form>

處理頁 (submit.php)

<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') exit('Method not allowed');

$token = $_POST['token'] ?? '';
if (empty($token) || !isset($_SESSION['form_token']) || $token !== $_SESSION['form_token']) {
    // Token 無效或已使用
    http_response_code(400);
    exit('請勿重複提交或表單已過期,請重新整理頁面。');
}

// 驗證通過,銷毀 Token
unset($_SESSION['form_token']);

// 業務邏輯:儲存資料...
echo '提交成功!';
?>

進階:分散式環境適用的 Redis Token

<?php
// 產生 Token
$token = 'form_token:' . bin2hex(random_bytes(16));
$redis->setex($token, 3600, '1'); // 1小時有效
// 表單隱藏欄位帶入 $token

// 驗證並刪除(原子操作)
$lua = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
$valid = $redis->eval($lua, 1, $token, '1');
if (!$valid) exit('重複提交或 Token 無效');
?>

方案三:資料庫唯一鍵/冪等鍵(業務層面)

訂單系統常用:建立訂單前先插入 `idempotency_key` (唯一索引),重複請求會拋出 Duplicate Entry 錯誤,直接回傳既有結果。

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    idempotency_key VARCHAR(64) NOT NULL UNIQUE,
    user_id INT NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

最佳實踐總結

  1. **前端** 必須有即時回饋(禁用按鈕、Loading 狀態),提升體驗。
  2. **後端** 必須實作 Token 驗證(Session 或 Redis),確保冪等性。
  3. **資料庫** 關鍵業務(下單、付款、扣款)加上唯一冪等鍵,做最後防線。
  4. **API** 設計應支援 `Idempotency-Key` 標頭,讓客戶端安全重試。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved