PHP刷新页面时CSRF Token每次新增2个而非1个的问题排查
问题根因
你的getToken()函数本身没有语法错误,每次脚本执行时会固定生成1个新Token写入Session。刷新页面新增2个Token的核心原因是单次页面访问过程中,你的PHP脚本被触发执行了2次,多出来的Token完全是额外请求导致的。
常见触发额外请求的场景:
- 浏览器自动请求站点favicon图标:如果你的项目没有提供favicon文件,且Web服务器配置了所有请求都重写到PHP入口文件(单入口框架/路由的通用配置),favicon请求会完整执行一遍PHP业务逻辑,额外生成1个Token
- 页面存在空地址资源标签:例如
<img src="">、<script src="">、<link href="">这类属性为空的标签,浏览器会默认将空地址解析为当前页面地址,自动发起一次额外的同路径GET请求 - 浏览器插件/开发者工具自动发起的请求:部分密码管理、广告拦截类插件,或是开启了DevTools的资源预加载功能,会在主页面请求外额外发起一次同地址探测请求
你可以在脚本最顶部加入临时调试日志,快速确认额外请求来源:
// 调试用,记录所有触发脚本执行的请求 file_put_contents( './request_debug.log', sprintf("[%s] URI: %s | UA: %s\n", date('Y-m-d H:i:s'), $_SERVER['REQUEST_URI'], $_SERVER['HTTP_USER_AGENT']), FILE_APPEND );
刷新一次页面后查看日志,就能看到同一时间段内存在2条请求记录,对应2次Token生成行为。
修复方案
第一步:拦截无效额外请求
- 配置Web服务器直接拦截favicon请求,不要让它走PHP业务逻辑,Nginx参考配置:
location = /favicon.ico { access_log off; log_not_found off; return 204; }
如果使用Apache,可以在.htaccess中加入规则:
<Files "favicon.ico"> Require all denied </Files>
- 检查页面源码,删除所有
src/href属性为空的资源标签 - 可以通过请求头字段过滤非主文档请求:主页面请求的
Sec-Fetch-Dest头值为document,静态资源、异步请求的该字段值不同,可在业务入口判断,非主文档请求不执行Session初始化和Token生成逻辑
第二步:优化CSRF Token生成逻辑
你当前的Token存储逻辑本身存在设计缺陷:即使没有额外请求,每次页面刷新都会永久留存一个历史Token,会导致Session体积无限膨胀。
常规CSRF防护不需要存储大量历史Token,用户会话有效期内单个有效Token即可满足需求,修改后的代码参考:
function getToken() { // 已存在有效Token时直接返回,不重复生成 if (isset($_SESSION['csrf_token']) && is_string($_SESSION['csrf_token']) && strlen($_SESSION['csrf_token']) === 64) { return $_SESSION['csrf_token']; } $token = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = $token; return $token; } $token = getToken(); var_dump($_SESSION);
如果你需要支持多标签页同时提交表单的多Token场景,建议给每个Token加上过期时间戳,每次生成新Token时自动清理超过预设有效期(比如1小时)的旧Token,避免无效数据堆积。
内容的提问来源于stack exchange,提问作者Jurij Rizhov
相关产品推荐
相关产品推荐

