必须用random_bytes()生成token并存入session,验证需三重检查(存在性、匹配性、时效性),且立即销毁;ajax应通过请求头传递token;集群环境须用redis等共享存储session。

PHP原生实现:用random_bytes()生成Token并存入Session
必须用random_bytes()生成,不能用mt_rand()或time()拼接——前者可预测,后者无熵,攻击者能爆破出有效token。生成后立刻存进$_SESSION,别等表单提交时再生成,否则GET页面没token,POST必失败。
推荐写法:
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = [
'value' => $token,
'expires' => time() + 1800
];
注意三点:
① bin2hex()只是编码,核心安全来自random_bytes(32);
② 存数组而非纯字符串,方便后续加时间戳和状态标记;
③ 不要存到$_COOKIE或前端可读位置,否则XSS场景下直接泄露。
验证时必须三重检查:存在性、匹配性、时效性
只比对$_POST['csrf_token'] === $_SESSION['csrf_token']['value']是严重漏洞。漏掉任一环,防护就形同虚设。
- 先确认
$_POST里有csrf_token字段,且非空(防止空值绕过) - 再用
hash_equals()比对,避免时序攻击 - 最后检查
time() ,超时直接拒收
验证通过后立即执行:unset($_SESSION['csrf_token'])。不销毁=允许重放。若需支持多表单(如编辑页+评论框共存),改用独立键名,例如$_SESSION['csrf_token_profile']和$_SESSION['csrf_token_comment']。
AJAX请求的Token传法与后端读取差异
HTML表单靠隐藏域,AJAX必须走请求头或请求体,两者逻辑不能混用。常见错误是前端把token塞进data里,后端却从getallheaders()读,结果永远校验失败。
推荐做法:
- 前端渲染时把token写进
<meta name="csrf-token" content="<?php echo htmlspecialchars($_SESSION['csrf_token']['value'] ?? ''); ?>"> - AJAX请求头加:
X-CSRF-Token: <?php echo $_SESSION['csrf_token']['value'] ?? ''; ?> - 后端读取:
$headers = getallheaders(); $token = $headers['X-CSRF-Token'] ?? '';(Apache环境需确认mod_headers启用,且Header名未被重写)
别把token放URL参数里——会被日志、代理、Referer泄露;也别用document.cookie读取后拼进AJAX数据,XSS下等于主动交出去。
高防CDN或集群环境下Token存储不能依赖本地文件
如果你用了蓝易云、腾讯云WAF或Nginx负载均衡,$_SESSION默认写文件可能不同节点读不到,导致“页面刷新后POST总失败”。这时必须切换存储后端。
可行方案:
- 改用Redis存session:
ini_set('session.save_handler', 'redis');,确保所有节点共享同一份csrf_token - 或用独立Token方案:
generate_standalone_token()生成带加密元数据的token,存数据库或Redis,用唯一ID关联请求 - 绝对不要在CDN层缓存含表单的页面——否则用户看到的是别人生成的token,提交必炸
最易被忽略的点:验证逻辑必须放在入口脚本最开始处,比如index.php顶部或框架中间件第一层。放到业务逻辑后面,等于先执行了转账再验身份。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











