php生成csrf token必须用bin2hex(random_bytes(32)),存入$_session['csrf_token'],表单中用htmlspecialchars()转义输出,校验时用hash_equals()且立即重置,严禁使用time()、rand()等可预测方式。

PHP里生成CSRF token必须用 bin2hex(random_bytes(32))
直接用 time()、rand() 或 md5(uniqid()) 生成的值可预测,攻击者能批量猜出。PHP 原生不提供 csrf_token() 函数,你得自己写。
正确做法是:
-
random_bytes(32)保证密码学安全随机性,bin2hex()转成字符串便于存储和输出 - 必须存进
$_SESSION['csrf_token'],不能存在$_COOKIE、localStorage或 URL 参数里 - 每次调用
session_regenerate_id(true)(比如登录成功后)必须紧接着重新生成 token,否则旧 session 数据还在,但 ID 已换,token 实际失效 - 建议封装为
ensure_csrf_token()函数,在每个需要表单的页面开头显式调用
表单中输出 token 必须用 htmlspecialchars() 转义
很多人直接写 <input value="<?php echo $_SESSION['csrf_token']; ?>">,这会引发属性注入:如果 token 里意外含双引号或 >,HTML 就被破坏,甚至执行 JS。
安全写法只有一种:
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'] ?? ''); ?>">- 不要用 JS 动态填充 value,比如
document.querySelector('[name=csrf_token]').value = token—— XSS 一触发,token 就泄露 - 多个 form 共用一个 token 可以,但校验通过后必须立刻
unset($_SESSION['csrf_token'])或重置,防止重放
校验时必须用 hash_equals(),不能用 ===
用 === 比较会暴露时序差异:攻击者发大量请求测响应时间,就能逐字节爆破出 token。尤其 hex 字符串长度固定,更容易被利用。
校验逻辑要严格:
- 先检查
$_POST['csrf_token']是否存在且非空,再检查$_SESSION['csrf_token']是否存在且非空 - 然后用
hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])—— 它恒定时间,不依赖字符串内容 - 校验通过后,必须立即重置:
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));,不是unset()后不管 - 失败时统一返回 HTTP 400 或跳转错误页,别返回 403/419 并附带提示文本 —— 会暴露校验逻辑是否触发
多标签页、长页面提交失败不是 bug,是设计副作用
用户开两个 tab 编辑同一页面,或表单挂了 20 分钟再提交,token 大概率已失效。这不是代码写错了,而是“一次性 + 绑定 session”机制的必然结果。
体验上可做有限兜底:
- 确保
session.gc_maxlifetime≥ 1440(24 分钟),太短会导致正常操作也频繁失败 - 前端可监听表单 submit,捕获 400 响应后自动刷新页面(带新 token),而不是静默报错
- 不要为了“兼容多 tab”而共享 token 或延长有效期 —— 那等于放弃防重放能力
- 敏感操作(如改密码、转账)建议额外加二次验证(如当前密码),不单靠 token
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











