CSRF过滤器必须显式注册,否则所有POST请求绕过验证;需在Filters.php的$globals['before']中添加'csrf',表单用csrf_field()生成token,AJAX需从Cookie读取并设X-CSRF-TOKEN头,CSRF不防重复提交,需结合Session Token或PRG模式。

CSRF过滤器必须显式注册,否则所有POST请求都绕过验证
CI4的CSRF防护不是开个开关就生效的,它依赖过滤器机制介入请求生命周期。如果你只改了Security.php里的$csrfProtection,但没在Filters.php中注册csrf过滤器,那表单提交完全不会校验token——此时看似能提交,实则毫无防护。
正确做法是打开app/Config/Filters.php,在$globals数组里加上:
public $globals = [
'before' => [
'csrf' => ['except' => ['api/*']], // 除API路由外全部启用
],
];
注意:except不是必需项,但若你有无状态API接口,必须排除,否则会因缺少token直接返回403。
- 不要只配
$methods['post'] = ['csrf']——这仅对纯POST请求生效,而现代表单常走application/json或带X-Requested-With头,before更稳妥 -
csrf过滤器默认只拦截POST、PUT、PATCH、DELETE方法,GET和HEAD不受影响 - CI4.7.2已废弃
$config['csrf_protection']这类CI3风格配置,别去app/Config/App.php里找它
表单必须用csrf_field(),手写隐藏域容易漏掉动态刷新逻辑
CI4的CSRF token默认启用$csrfRegenerate = true(在app/Config/Security.php中),这意味着每次请求都会生成新token,旧token立即失效。如果手写,value值不会随页面刷新更新,第二次提交必然失败。
务必使用框架提供的辅助函数:
<?php echo csrf_field(); ?>
它等价于:
<input type="hidden" name="<?= csrf_token_name() ?>" value="<?= csrf_hash() ?>">
-
csrf_field()自动适配$csrfTokenName和$csrfCookieName配置,不硬编码字段名 - 若表单是AJAX提交,前端需从Cookie读取token(默认名
ci_csrf_token),并在请求头加X-CSRF-TOKEN: xxx - 不要在JS里用
document.querySelector('[name=csrf_test_name]').value取值——DOM可能还没渲染完,应优先读Cookie
CSRF本身不防重复提交,需配合Session Token或PRG模式
CSRF防护只验证“请求是否来自本域合法页面”,它不阻止用户连点两次提交按钮,也不防止F5刷新重发。你看到的“重复提交”现象,往往是CSRF未生效+缺乏业务层幂等控制共同导致的。
真正防重复提交要叠加一层机制:
- 用Session存一个一次性
submit_token:表单页生成并存入Session,提交时比对并unset(),匹配后才进业务逻辑 - 强制PRG(Post-Redirect-Get):控制器处理完POST后,必须用
return redirect()->to('success')->with('message', 'ok');,避免用户刷新导致重复POST - 数据库唯一约束仍是兜底:比如订单号设为
UNIQUE,配合try/catch DuplicateEntryException返回友好提示
CI4.7.2的csrf过滤器不会自动帮你做这些——它只管token存在性和时效性,业务幂等性得自己补。
调试时先确认token是否被正确传递和校验
遇到403却找不到原因?别急着改配置,先抓关键点:
- 浏览器开发者工具Network面板,看提交的POST请求里是否有
csrf_test_name=xxx字段(字段名以Security.php中$csrfTokenName为准) - 检查响应Headers里有没有
Set-Cookie: ci_csrf_token=xxx,没有说明过滤器根本没运行 - 临时在
app/Config/Filters.php的csrf过滤器类里加log_message('debug', 'CSRF triggered');,确认它是否被调用 - CI4.7.2默认把CSRF token存在Cookie里,若你的站点启用了
SameSite=None且没配Secure,跨域请求时Cookie可能被浏览器丢弃
最常被忽略的是:本地开发用http://localhost,但前端代码里写了https://yourdomain.com发请求,导致Cookie域不匹配,token始终为空。


















