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

PHP 防止表單重複提交:Token 機制與 Session 方案深度解析

     編輯:PHP 相關知識

為何需要防重複提交?

用戶雙擊提交按鈕、瀏覽器刷新、後退鍵重新提交、網絡重試等場景,都會導致同一表單數據被多次寫入資料庫,造成訂單重複、積分多扣、垃圾數據等嚴重問題。

核心策略:同步令牌模式

原理:表單渲染時生成唯一 Token 存入 Session 並嵌入表單隱藏域;提交時比對 Token,匹配則處理並銷毀 Token,不匹配則拒絕。

完整實現代碼

<?php
// form.php - 表單頁
session_start();

// 生成 Token
$token = bin2hex(random_bytes(32));
$_SESSION['form_token'] = $token;
?>
<!DOCTYPE html>
<html lang="zh-TW">
<head><meta charset="UTF-8"><title>提交表單</title></head>
<body>
    <form action="submit.php" method="post">
        <input type="hidden" name="token" value="<?php echo $token; ?>">
        <label>內容:<input type="text" name="content" required></label>
        <button type="submit">提交</button>
    </form>
</body>
</html>
<?php
// submit.php - 處理頁
session_start();

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    exit('Method Not Allowed');
}

// 1. 驗證 Token 存在
if (empty($_POST['token']) || empty($_SESSION['form_token'])) {
    die('無效請求:Token 缺失');
}

// 2. 比對並銷毀(一次性使用)
if (!hash_equals($_SESSION['form_token'], $_POST['token'])) {
    die('請勿重複提交或表單已過期');
}

// 驗證通過,銷毀 Token 防止二次提交
unset($_SESSION['form_token']);

// 3. 業務邏輯處理
$content = trim($_POST['content']);
// ... 資料庫寫入、發送郵件等操作

echo '提交成功!';
?>

進階:支持多表單並發

為避免用戶開啟多個標籤頁導致 Token 衝突,可採用Token 陣列或命名空間 Token:

<?php
// 生成帶表單標識的 Token
$formKey = 'contact_form';
$token = bin2hex(random_bytes(32));
$_SESSION['form_tokens'][$formKey] = $token;

// 驗證時
$formKey = $_POST['form_key'] ?? 'default';
if (!isset($_SESSION['form_tokens'][$formKey]) ||
    !hash_equals($_SESSION['form_tokens'][$formKey], $_POST['token'])) {
    die('Token 驗證失敗');
}
unset($_SESSION['form_tokens'][$formKey]);

前端輔助防禦(非替代方案)

<script>
// 提交後禁用按鈕
document.querySelector('form').addEventListener('submit', function() {
    this.querySelector('button[type="submit"]').disabled = true;
    this.querySelector('button[type="submit"]').textContent = '提交中...';
});
</script>

常見誤區與最佳實踐

誤區正確做法
僅靠 JS 禁用按鈕JS 可繞過,必須服務端驗證
用 IP 或 Session ID 直接判斷無法區分正常重新提交與惡意重試
Token 不銷毀必須一次性,驗證通過即銷毀
用 md5/uniqid 生成 Token使用 random_bytes() 保證熵值

分佈式環境擴展

多服務器集群下,Session 需共享(Redis、資料庫),或改用JWT 無狀態 Token:

<?php
// 簽發 Token(有效期 10 分鐘)
$payload = ['form' => 'contact', 'exp' => time() + 600];
$token = JWT::encode($payload, $secretKey);

// 驗證
try {
    $data = JWT::decode($token, $secretKey, ['HS256']);
    // 可選:在 Redis 記錄已使用的 jti 防重放
} catch (Exception $e) {
    die('Token 無效');
}

總結:服務端 Token 機制是核心,前端禁用按鈕僅為體驗優化,分佈式場景下引入 Redis 或 JWT 即可平滑擴展。

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