csrf令牌验证失败主因是配置、加载或输出问题:需启用csrf_protection、加载form辅助函数,确保token_name/cookie_name非空,避免表单嵌套冲突;403提示表明校验失败,应检查form data中字段是否存在及有效,排查session、代理过滤,并注意ajax需手动传令牌且令牌一次性有效。

CSRF令牌验证失败,大概率不是 form_open() 写错了,而是 CSRF 配置、加载时机或输出位置出了问题。
form_open() 生成的表单为什么没带 CSRF 字段?
默认情况下,form_open() 只有在 CSRF 保护启用且 form 辅助函数已加载的前提下,才会自动插入 <input type="hidden" name="csrf_test_name" value="...">。它不依赖你是否手动调用 csrf_field()(那是 CI4 的写法,CI3 没这函数)。
- 必须先加载辅助函数:
$this->load->helper('form');,否则form_open()根本无法调用 - CI3 中 CSRF 开关由
app/config/config.php的$config['csrf_protection'] = TRUE;控制,设为FALSE或注释掉就完全不生成令牌字段 - 即使开启,若
$config['csrf_token_name']或$config['csrf_cookie_name']被意外覆盖为空字符串,也会导致字段名非法、值为空,后端校验直接失败 - 模板中若存在多个
form_open()嵌套(比如在循环里反复调用),可能因内部静态变量冲突导致后续表单漏掉隐藏字段(CI3.1.11 及更早版本有此 bug)
表单提交报 403 或 “The action you have requested is not allowed.”
这是 CI3 默认的 CSRF 拒绝响应文案,说明请求到达了框架层,但令牌校验失败。重点排查以下三点:
- 检查浏览器开发者工具 Network 标签页,点开该 POST 请求,看 Form Data 里是否存在
csrf_test_name字段(或你自定义的$config['csrf_token_name']值)及其对应值;没有 → 前端没生成;有但值为空 → 后端 session 未写入或 cookie 被拦截 - 确认 PHP session 是否正常工作:
session_start()未被禁用、session.save_path目录可写、无输出提前发送 header(如 BOM、echo、var_dump) - 如果使用了反向代理(Nginx/Apache)、CDN 或某些安全插件,它们可能过滤掉带下划线的字段名(如
csrf_test_name),可临时改名为不含下划线的(如token)测试
form_open() 的第三个参数传隐藏字段,会覆盖 CSRF 字段吗?
不会覆盖,但会「并列插入」——form_open($action, $attributes, $hidden) 中的 $hidden 数组内容会生成额外的 <input type="hidden">,而 CSRF 字段是框架内部追加的,始终在最末尾(CI3 行为)。不过要注意:
- 如果你在
$hidden里手动写了和 CSRF 同名的 key(例如['csrf_test_name' => 'xxx']),会导致两个同名字段,后端取值时可能拿到你伪造的旧值,从而绕过校验或触发冲突 - CI3 不校验 CSRF 字段是否重复,只取第一个匹配的值,所以重复字段等于自毁防护
- 上传表单必须用
form_open_multipart(),它和form_open()共享同一套 CSRF 逻辑,无需额外处理
AJAX 提交时 CSRF 校验失败的典型原因
CI3 的 CSRF 机制默认只对常规表单提交生效,AJAX 请求需手动携带令牌,且不能只靠 Cookie 自动发送:
- 前端必须从页面源码中提取 CSRF 字段值(例如
document.querySelector('input[name="csrf_test_name"]').value),不能依赖document.cookie—— 因为 CI3 默认把令牌存进 session,不是 Cookie(除非你显式设置了$config['csrf_cookie_name']且启用了 cookie 模式) - AJAX 请求头里加
X-CSRF-Token: xxx是无效的,CI3 只认 POST body 或 query string 中的字段名,不解析请求头 - 若用 jQuery,推荐在全局
beforeSend中统一补字段:data: {'csrf_test_name': csrf_token},而不是拼在 URL 后(GET 参数易被日志记录)
真正容易被忽略的是:CI3 的 CSRF 令牌在每次有效请求后默认会刷新($config['csrf_regenerate'] = TRUE),这意味着同一个令牌只能用一次。如果页面含多个异步表单或重复提交按钮,没做防重或令牌同步,第二次提交必然失败。










