TOKEN_ERROR 根源是后端校验时未获取匹配token,主因包括前端未传、session未存、URL/路径/参数不一致;需确保{:token()}在form内正确渲染、session启用且有效、AJAX手动传递token。

_TOKEN_ERROR_ 报错不是 token 生成失败,而是后端校验时根本没拿到匹配的值——要么前端没传,要么后端 session 里压根没存,或者两者时间/路径/参数不一致。
token() 函数调用时机和位置不对
模板里写 {:token()}(V6)或 {:csrf_token()}(V5.1+)必须在 <form> 标签内部,且不能被缓存、CDN 或 layout 布局层提前渲染后又覆盖。常见错误是:在公共 header 模板里写了 token,但实际表单在子模板里,结果页面源码中出现两个 __token__ 隐藏域,后一个覆盖前一个,提交时用的是空值或旧值。
- 检查浏览器“查看源码”,确认只有一个
<input name="__token__" value="...">,且value不为空 - 不要在 JS 里手动拼接表单或用
innerHTML替换 form 内容,会丢掉动态生成的 token - 如果用了多级模板继承(比如 extend + block),确保
{:token()}在最终渲染的 block 内,而不是被父模板 static 化了
Session 未启用或跨请求失效
_TOKEN_ERROR_ 的底层逻辑是比对 $_POST['__token__'] 和 $_SESSION['__token__'][$key],只要 session 启动失败、路径写错、域名不匹配、或 PHP 进程重启导致 session 文件丢失,校验就必然失败。
- 确认入口文件(如
public/index.php)顶部已执行session_start(),或配置中'session' => true已开启 - 检查
session.save_path对应目录是否可写,Linux 下常见权限为www-data用户无权写入 - 若用子域名访问(如
admin.example.com),需显式设置session.cookie_domain = '.example.com' - V6 默认使用
think\session\driver\File,若部署在容器或无状态环境,考虑切到 Redis 驱动
URL 参数或路由导致 token 不一致
ThinkPHP V6 的 token() 默认基于完整 URL(含 query string)生成 hash key。例如 /post/edit?id=1 和 /post/edit?id=2 生成的 token 完全不同,即使页面模板一样。
- 表单页带参数时,务必在生成 token 时显式忽略 query:用
{:token(null, false)}(第二个参数false表示不包含 URL 查询参数) - 避免在 GET 请求中动态拼接 tab、page 等参数后直接刷新表单页,会导致 token key 变更
- 如果必须保留参数一致性,可在控制器中统一处理:在分配模板前调用
token(null, false)并 assign 给模板变量,前端用该值填入 hidden input
AJAX 提交时 token 传递遗漏
原生 form 提交自动带上 __token__ 字段,但 AJAX 默认不会。后端收不到该字段,autoCheckToken() 直接返回 false。
立即学习“PHP免费学习笔记(深入)”;
- 前端必须从页面 DOM 中读取:
document.querySelector('input[name="__token__"]').value,再附到 data 或 headers 里 - 若用 fetch,推荐加到 body:
body: new URLSearchParams({...data, __token__: tokenValue}) - 若用 axios,可在拦截器中统一注入:
config.data.__token__ = document...value(注意判断是否存在) - 后端不要依赖
$_GET或 header 里的 XSRF-TOKEN,ThinkPHP 默认只认$_POST['__token__'],除非你重写了验证逻辑
_TOKEN_ERROR_ 就准时报到。



















