ThinkPHP 6 的 token() 函数仅生成一次性隐藏字段,不自动校验;必须启用 session、使用非 GET 请求、配置 ValidateToken 中间件或手动调用 validateToken() 才生效,否则形同虚设。

ThinkPHP 6 的 token() 不是“加了就防住”,它只生成字段;校验必须显式启用,否则形同虚设。
token() 函数到底干了什么
它只是调用 think\helper\Str::random(16) 生成随机盐,再与 session_id、控制器名、操作名、时间戳拼接后加密,输出一个 <input type="hidden" name="__token__" value="xxx">。不启动 session、不在 POST/PUT/DELETE 请求里、没配中间件或手动校验,这个值就纯属装饰。
- 模板中写
{:token()}是唯一合法写法;{:csrf_token()}直接报Undefined function csrf_token - 同一页面多次调用
{:token()},会生成多个不同__token__字段,导致校验必错 - 如果模板缓存关闭,页面未刷新但 JS 复用了旧 DOM 中的
value,提交时就会拿过期 token
为什么 validateToken() 总返回 false
常见错误不是函数写错,而是环境没对齐——$this->validateToken(input('post.')) 依赖三个前提同时成立:
- 当前请求已启动 session(CLI、API 接口、无 session 的路由下必然失败)
-
input('post.')确实包含__token__键,且字段名和配置的token_name一致(默认是__token__,不是_token) - 服务端 session 存储可跨请求读取(负载均衡下 Redis 未共享,token 在 A 机器生成、B 机器校验,直接丢弃)
POST 提交后页面要保留表单,怎么避免 token error
ThinkPHP 默认 token 一次性消耗,校验通过后自动销毁。用户填错重定向回原页时,{:token()} 渲染的是旧值,而 session 里已为空——所以必须在返回前重建。
立即学习“PHP免费学习笔记(深入)”;
- 控制器中校验失败后,立即调用
TokenService::reset()(TP6.1+)或TokenService::buildToken(true)强制刷新 - 不要在 JS 里提前缓存
document.querySelector('input[name="__token__"]').value,那会锁死过期值 - 若用 AJAX 提交,建议单独提供
/api/token接口返回token()结果,每次提交前 fetch 一次
多表单共存或长周期页面怎么处理
默认策略不适合真实业务:一个页面两个表单、用户停留超 30 分钟、多标签页反复打开,都会触发失效。不能靠“禁用校验”妥协,得改用可控方式。
- 生成时传参:
token(true)表示不删除原 token(允许多次使用),token(false, true)表示跳过时间戳校验(避免过期) - 更稳妥的是绕过自动机制:用
TokenService::buildToken($name)手动为每个表单生成独立 token,并用TokenService::checkToken($value, $name)显式比对 - 自定义
token_name后,必须同步改中间件配置、模板调用、JS 提交逻辑,漏一处就全链路断掉
真正容易被忽略的点是:token 生效与否,完全取决于 session 生命周期和部署一致性。开发机上好好的,上线后负载均衡没配 Redis 共享 session,或者 Nginx 把带 cookie 的请求轮询到了不同 PHP-FPM 实例——这时候查日志只会看到 “token error”,但问题根子在基础设施层。



















