用戶提交表單後按 F5 刷新,瀏覽器會重發 POST 請求,導致:
利用 Session 在服務端記錄「已處理的 Token」,每次提交攜帶新 Token,處理成功後標記舊 Token 失效、生成新 Token。刷新時舊 Token 已失效,直接拒絕。
重新載入');
}
// 業務邏輯(這裡只演示輸出)
echo "提交成功!數據:
";
print_r($_POST);
echo "";
// 3. 關鍵:消耗 Token,生成新 Token(防刷新重複)
$_SESSION['form_token'] = bin2hex(random_bytes(16));
// 4. PRG 模式:重導向到 GET 頁面,徹底杜絕重發
header('Location: ' . $_SERVER['PHP_SELF'] . '?success=1');
exit;
}
?>
防重複提交演示
✅ 處理完成,刷新頁面不會重複提交。
單一 Token 會導致多標籤頁互相衝突。改用「Token 池」:
// 生成 Token 並存入池(最多保留 5 個)
$_SESSION['token_pool'] = $_SESSION['token_pool'] ?? [];
$_SESSION['token_pool'][] = $newToken = bin2hex(random_bytes(16));
if (count($_SESSION['token_pool']) > 5) array_shift($_SESSION['token_pool']);
// 驗證時從池中查找並移除
$submitted = $_POST['_token'] ?? '';
$pool = &$_SESSION['token_pool'];
$key = array_search($submitted, $pool, true);
if ($key === false) die('Token 無效或已過期');
unset($pool[$key]); // 一次性消耗
$pool = array_values($pool); // 重排索引
302/303 Location: /success-page黃金法則: 所有修改數據的操作(增/刪/改)必須走 PRG;純查詢(搜尋、列表、詳情)走 GET。
| 誤區 | 正確做法 |
|---|---|
| 只用 JS 禁用按鈕 | JS 可被繞過,必須有服務端 Token 驗證 |
用 $_SESSION['submitted'] = true 簡單標記 | 無法支援多標籤、多表單並發,需用 Token 池 |
| 驗證通過後不重導向,直接輸出結果頁 | 違反 PRG,用戶刷新仍會重發 POST |
| Token 存入資料庫 | 過度設計,Session 存儲已足夠(單機/集群共享 Session 即可) |
@csrf Blade 指令 + VerifyCsrfToken 中間件,已自動實現 Token 驗證 + PRG。CsrfTokenManager + Form 組件自動處理。token 驗證規則 + redirect() 實現 PRG。CsrfMiddleware 或 Trait 復用上述邏輯。「Session Token 去重 + PRG 重導向」是防止表單重複提交的標準解法,代碼量極小、零依賴、適用於所有 PHP 專案。請務必在每個「寫入型」表單中落地執行。