必须用$_session存一次性token并提交后立即unset(),校验须用hash_equals()或===;禁用md5(time())等弱随机源,应使用bin2hex(random_bytes(32))生成高熵token,且需session_start()后调用、避免重复生成,校验前检查变量存在性,失败即exit,成功后立即销毁token,并配合post-redirect-get防止刷新重复提交。

直接结论:必须用 $_SESSION 存一次性 token,提交后立刻 unset(),且校验必须用 hash_equals() 或 ===。 任何跳过销毁、复用 token、或只靠前端禁用按钮的做法,都等于没防。
怎么生成安全的 form_token?
别用 md5(time()) 或 uniqid() —— 它们可预测、熵低,容易被爆破重放。
- PHP 7+ 推荐:
bin2hex(random_bytes(32)),生成 64 位十六进制随机串,抗碰撞强 - 必须在
session_start()之后调用,否则$_SESSION不可用 - 不要在表单渲染前多次生成(比如循环里),否则最终塞进隐藏域的可能是最后一个值,而 session 里存的是中间某个,导致必不匹配
表单提交时怎么校验才不被绕过?
常见错误是写成 == 或直接 if ($_POST['token'] == $_SESSION['form_token']) —— 这会触发 PHP 类型转换,比如 "0" == 0 成立,攻击者发 token=0 就能绕过。
- 必须用
hash_equals($_SESSION['form_token'], $_POST['token'])(推荐)或===严格比较 - 校验前先检查
isset($_POST['token'])和isset($_SESSION['form_token']),缺一即拒 - 校验通过后第一件事就是
unset($_SESSION['form_token']),不是“处理完再删”,更不能只设为空字符串 - 校验失败要
exit或明确返回错误,绝不能继续执行插入/支付等业务逻辑
为什么刷新成功页还会重复提交?
这是 POST-Redirect-GET 模式没做对的典型表现。用户提交成功后如果直接输出结果页 HTML,而不是 header('Location: success.php') 跳转,那浏览器地址栏仍停留在 POST URL,F5 刷新就会重发。
- 务必在业务处理完成后、任何输出之前调用
header('Location: ...') - 跳转前确认没 echo、没空格、没 BOM —— 否则报
headers already sent - 跳转目标页(如
success.php)只能接收 GET 参数,不能依赖 POST 数据,否则又得带 token 处理,逻辑变重 - 如果必须在当前页显示结果(比如 AJAX 场景),那就得靠前端 loading 状态 + 后端 token 销毁双保险
最容易被忽略的点:用户开两个标签页同时填同一张表单,第二个提交必然失败——这不是 bug,是设计使然。如果你的应用需要支持多页并发操作,就得为每个表单实例分配独立 token(比如加时间戳或页面 ID 做后缀),而不是全局共用一个 $_SESSION['form_token']。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











