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

PHP 防止表單重複提交的多種實作方案與最佳實踐

     編輯:PHP 相關知識

PHP 防止表單重複提交的多種實作方案與最佳實踐

表單重複提交是 Web 開發中常見的問題,使用者可能因網路延遲多次點擊提交按鈕,或透過重新整理、瀏覽器前進後退導致重複請求。若無防禦機制,將造成資料庫髒資料、重複扣款等嚴重後果。本文介紹三種主流防重複提交方案,並提供生產環境可用的最佳實踐程式碼。

方案一:Session + Token 驗證(推薦)

核心原理:在表單生成時產生唯一 Token 存入 Session 並嵌入表單隱藏欄位,提交時驗證 Token 是否匹配且未被使用過。

<?php
session_start();

// 生成表單時執行
function generateFormToken() {
    $token = bin2hex(random_bytes(32)); // 加密安全的隨機 token
    $_SESSION['form_token'] = $token;
    $_SESSION['token_time'] = time();
    return $token;
}

// 處理表單提交時驗證
function validateFormToken($submittedToken) {
    if (empty($_SESSION['form_token']) || empty($submittedToken)) {
        return false;
    }
    // 驗證 token 並立即失效(一次性使用)
    $valid = hash_equals($_SESSION['form_token'], $submittedToken);
    unset($_SESSION['form_token']);
    return $valid;
}

// 檢查 token 時效性(防止長期佔用)
function isTokenExpired($maxAge = 3600) {
    return isset($_SESSION['token_time']) && (time() - $_SESSION['token_time'] > $maxAge);
}
?>

表單頁面:

<form method="POST" action="submit.php">
    <input type="hidden" name="csrf_token" value="<?php echo generateFormToken(); ?>" />
    <!-- 其他表單欄位 -->
    <button type="submit">提交</button>
</form>

處理頁面:

<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (validateFormToken($token)) {
        // Token 驗證通過,處理業務邏輯
        echo '提交成功';
    } else {
        // Token 無效或已使用
        echo '請勿重複提交,或表單已過期,請重新整理頁面';
    }
}
?>

方案二:資料庫唯一約束(業務層防禦)

在資料表關鍵欄位(如訂單號、用戶 ID + 業務類型)建立唯一索引,資料庫層面直接阻擋重複寫入。

ALTER TABLE `orders` ADD UNIQUE INDEX `uk_user_biz` (`user_id`, `biz_type`, `biz_id`);

PHP 寫入時捕獲重複鍵錯誤(MySQL 錯誤碼 1062):

try {
    $pdo->beginTransaction();
    $stmt = $pdo->prepare('INSERT INTO orders (user_id, biz_type, biz_id, ...) VALUES (?, ?, ?, ...)');
    $stmt->execute([$userId, $bizType, $bizId, ...]);
    $pdo->commit();
} catch (PDOException $e) {
    $pdo->rollBack();
    if ($e->getCode() === '23000') { // 完整性約束違規
        throw new Exception('重複提交,資料已存在');
    }
    throw $e;
}

方案三:前端攔截(輔助層)

雖不可完全信任前端,但能大幅減少無效請求:

  • 提交按鈕點擊後立即 disabled 並顯示「處理中...」
  • 使用 localStorage 或 sessionStorage 記錄提交狀態
  • 配合 PRG 模式(Post-Redirect-Get),提交成功後重導向至結果頁,避免重新整理重複提交
// PRG 模式範例
if ($submitSuccess) {
    header('Location: success.php?order_id=' . $orderId);
    exit;
}

綜合最佳實踐建議

  1. 多層防禦:前端攔截 + Session Token + 資料庫唯一約束三層同時部署
  2. Token 安全性:使用 random_bytes + hash_equals 防止時序攻擊
  3. 一次性 Token:驗證通過立即銷毀,防止重放攻擊
  4. 時效控制:設定 Token 過期時間(建議 30-60 分鐘)
  5. 分散式環境:若使用多台伺服器,需將 Session 儲存於 Redis 等共享儲存
  6. 冪等性設計:業務介面設計為冪等操作,即使重複呼叫也不產生副作用

常見誤區提醒

  • ❌ 僅依賴 JavaScript 防重(使用者可關閉 JS 或直接發送 HTTP 請求)
  • ❌ 使用 IP 限制(NAT 環境下多用戶共用 IP,易誤傷)
  • ❌ Session Token 未及時銷毀,導致可重複使用
  • ❌ 未處理併發情況,需配合資料庫鎖或分散式鎖

正確實作防重複提交機制,是保障資料一致性、提升系統穩定性的關鍵環節。建議根據業務重要性選擇適當的防禦層級組合。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved