php表单防重复提交必须用session token校验并立即销毁,仅前端禁用按钮无效;核心是生成唯一token、表单隐藏传输、服务端比对后unset,配合prg模式形成“生成→透出→校验→销毁”闭环。

PHP 表单重复提交的典型表现
用户点一次提交按钮,后端却收到多条记录;或者刷新成功页时又插入一遍数据。这不是前端防不住,而是没在服务端设防——$_POST 数据照收不误,数据库就默默执行了多次 INSERT。
常见诱因包括:用户手抖连点、页面加载慢导致误以为没提交、F5 刷新成功页(浏览器重发上一个 POST 请求)、后退再提交。
用 session token 拦住重复 POST
核心思路是:每次生成唯一 token,表单里藏一个隐藏字段,提交时校验并立即销毁。只要 token 对不上或已用过,就拒绝写入。
- 生成 token 用
bin2hex(random_bytes(16)),别用md5(time())这类可预测值 - 把 token 存进
$_SESSION['form_token'],同时塞进表单:<input type="hidden" name="token" value="<?php echo $_SESSION['form_token']; ?>"> - 接收时先比对:
if (!hash_equals($_SESSION['form_token'] ?? '', $_POST['token'] ?? '')) { die('非法请求'); } - 比对通过后立刻清空:
unset($_SESSION['form_token']),否则同一 token 可被反复利用
为什么不能只靠前端 disabled 按钮
前端禁用按钮只是体验优化,绕过太容易:禁用状态可被 JS 移除、curl 直接发包、甚至浏览器开发者工具改 HTML 后重发。
立即学习“PHP免费学习笔记(深入)”;
更隐蔽的问题是:有些框架(如 Laravel 的 @csrf)默认只校验 token 是否存在,不自动失效。如果没手动清除 session 中的 token,用户开两个标签页填同一表单,第二个提交就会失败——这不是 bug,是设计如此,但得心里有数。
关键区别:disabled 是防手抖,session token + unset 才是防重复。
POST-Redirect-GET(PRG)模式必须配 token
单纯做 302 跳转(比如提交后 header('Location: success.php'))能防止 F5 刷新重复提交,但解决不了“用户开两个窗口同时提交”或“网络卡顿导致前端重复发包”的情况。
所以 PRG 只是基础动作,真正兜底还得靠 token 校验:
- 表单页生成并存 token
- 处理页校验、写库、
unsettoken,再跳转 - 跳转后的页面(success.php)不再处理 POST,自然无法重复触发
漏掉 unset 这一步,token 就成了摆设;跳转前没校验就写库,PRG 也白搭。











