表單重複提交會導致重複訂單、多次扣款等嚴重業務問題。本文詳解基於 Session 的 Token 驗證機制,提供生產級可復用實現,涵蓋生成、驗證、自動刷新、多標籤頁支援。
class TokenManager {
private static $tokens = [];
public static function generate($formName = 'default') {
$token = bin2hex(random_bytes(32));
$_SESSION['tokens'][$formName] = $token;
return $token;
}
public static function validate($formName, $token) {
if (!isset($_SESSION['tokens'][$formName])) return false;
if (!hash_equals($_SESSION['tokens'][$formName], $token)) return false;
// 驗證成功後刷新
self::generate($formName);
return true;
}
public static function get($formName = 'default') {
return $_SESSION['tokens'][$formName] ?? self::generate($formName);
}
}
// 使用
$token = TokenManager::get('order_form');
// 模板輸出:
// 驗證
if (TokenManager::validate('order_form', $_POST['token'])) {
// 處理訂單
}
$('form').on('submit', function() {
const $btn = $(this).find('button[type="submit"]');
$btn.prop('disabled', true).text('提交中...');
// 可選:AJAX 提交
$.post(this.action, $(this).serialize(), function(res) {
$btn.prop('disabled', false).text('提交');
if (res.code === 0) location.reload();
else alert(res.msg);
}, 'json');
return false; // 阻止默認提交,由 AJAX 處理
});
| 場景 | 問題 | 解決方案 |
|---|---|---|
| 用戶連點提交按鈕 | 多次請求並發 | 前端按鈕 disabled + 後端 Token 驗證雙重保障 |
| 瀏覽器回退重新提交 | 舊 Token 仍有效 | 驗證後立即刷新 Token,舊 Token 失效 |
| 多標籤頁操作 | Token 衝突 | 按表單名稱區分 Token 存儲 |
| Session 過期 | Token 驗證失敗 | 友好提示引導用戶刷新頁面重試 |
| CSRF 攻擊 | 偽造請求 | Token 本質上即 CSRF 防護,需配合 SameSite Cookie |
hash_equals() 防止時序攻擊random_bytes() 生成,而非 md5(uniqid())SameSite=Strict Cookie 屬性防禦 CSRFToken 機制是 Web 安全基礎設施的核心組件,正確實現能有效阻斷 99% 的重複提交與 CSRF 攻擊。