thinkphp csrf防护需手动启用,否则{:token()}仅输出无效字段;常见“非法请求”源于校验未触发、缓存复用旧token、url参数影响或输入流被提前消耗。

ThinkPHP 的 CSRF 防护默认不生效,必须显式启用校验逻辑,否则 {:token()} 只是输出一个无用的隐藏字段。
为什么表单提交后报“非法请求”或“token error”
这不是攻击触发的,而是开发配置错位导致的典型现象:
-
validateToken()没被调用,或只在部分控制器里漏写了 - 模板中用了
{:token()},但控制器没做校验,或者校验时传入的是空数组(如input('post.')在非 POST 请求下返回空) - 页面被浏览器缓存,
{:token()}渲染了一次后反复复用旧值;而 ThinkPHP V6 默认基于完整 URL(含 query 参数)生成 token,?tab=1和?tab=2视为两个不同 token - 中间件或日志组件提前调用了
input()或file_get_contents('php://input'),导致原始输入流被消耗,validateToken()拿不到__token__字段
如何正确启用并控制 token 行为
ThinkPHP 不是“开箱即防”,关键动作要手动做:
- 在需要防护的路由定义中加
['token' => true],或在控制器里声明protected $middleware = [\think\middleware\TokenCheck::class]; - 模板中必须用
{:token()}(V6),不是{:csrf_token()}—— 后者会报Undefined function csrf_token - 若需多表单共存或页面刷新后重试,改用
token(true)(保留原 token 不删除)或token(null, false)(忽略 URL 查询参数影响) - AJAX 场景下不能依赖模板函数,应单独提供接口(如
GET /api/token)返回token()结果,前端动态注入到请求体
session 驱动变更后 token 校验总失败
Token 默认存在 session 里,换 Redis 或数据库等驱动时,容易因 session 读写不一致导致校验失败:
- 确认 session 配置中
use_trans_id关闭(ThinkPHP V6.1+),否则跨请求无法命中同一 session - Redis 驱动需确保
handler实例全局复用,避免每次 new 出新连接导致 session 写入丢失 - 检查是否启用了 session 自动启动(
session.auto_start = 0),ThinkPHP 要求由框架自己控制 session 生命周期 - 调试时可在校验前 dump
$this->app->session->get('__token__')和input('__token__'),确认两者是否匹配
多标签页、重复打开页面导致 token 失效
这是设计使然,不是 bug —— ThinkPHP 每次渲染 {:token()} 都会刷新 session 中的 token 值,旧页面的表单自然失效:
- 不要全局关掉 token(如禁用
middleware.csrf),应按需排除:在路由定义中加'except' => ['api/upload'] - 对允许重复提交的场景,启用复用机制:
token(null, true, true)(第三个true表示允许多次验证) - 更可控的做法是绕过自动机制:用
buildToken()手动生成并存入 session,再用checkToken($value)显式校验,完全掌控生命周期 - 注意
buildToken()和checkToken()在 V6.0+ 属于think\helper\Str,需手动引入或使用门面
真正难处理的不是“怎么加 token”,而是 token 的生命周期和业务流程是否对齐。比如支付回调页不该有 token,管理员批量操作可能需要延长有效期,这些都得脱离“默认配置”去单独适配。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











