Token生成必须绑定当前Session会话,否则token()返回空或固定值导致校验失败;校验失败后须手动清除Session中token确保一次有效;禁止重复生成或跨域/CLI环境使用;API应改用JWT等方案。

Token生成必须绑定当前Session会话
ThinkPHP的token()函数默认依赖session_start()已开启,且Token值写入$_SESSION中。如果页面没启Session、或Session被提前销毁(比如在中间件里调用了session_destroy()),token()生成的隐藏字段就只是个空字符串或固定字符串,后端校验必然失败。
常见错误现象:token_check()始终返回false,但表单HTML里确实有__token__字段;或者刷新页面后Token不变——说明Session未生效或被复用。
- 确保控制器/模板渲染前Session已启动:检查
session.auto_start是否关闭,推荐在应用入口或BaseController构造方法里显式调用session_start() - 不要在生成Token前执行
session_write_close()或手动清空$_SESSION - 若使用Redis等外部Session驱动,确认配置中
session.save_handler和session.save_path正确,否则Token写不进Session存储
校验失败时不能跳过Session销毁逻辑
ThinkPHP的token_check()只做比对,不自动清理。如果校验失败后仍保留原Token,用户刷新页面重提就会“意外通过”——因为Session里还是旧值,而表单提交带的也是旧__token__,形成绕过。
使用场景:支付页、评论提交、订单创建等强一致性要求操作,必须“一次有效”。哪怕校验失败,也要立刻让该Token失效。
立即学习“PHP免费学习笔记(深入)”;
- 手动调用
session('token', null)或unset($_SESSION['token']),确保下一次token()生成新值 - 不要把
token_check()放在验证器里单独调用——它不抛异常也不终止流程,得配合if (!token_check()) { ... }显式处理 - 注意:ThinkPHP 6.0+ 的
think\facade\Session中delete('token')等价于session('token', null),但clear()会清空全部Session,慎用
POST请求必须包含且仅包含一个__token__字段
前端多处调用{:token()}、或JS动态拼接表单时重复插入__token__,会导致input['__token__']变成数组,token_check()内部用is_string()判断直接返回false。
错误信息示例:Warning: is_string() expects parameter 1 to be string, array given in vendor/topthink/framework/src/think/Helper.php
- 检查模板中是否写了多次
{:token()},尤其注意include嵌套、widget组件里是否悄悄又生成了一次 - AJAX提交时,别用
$('form').serialize()——它会把所有同名字段合并成数组;改用new FormData(form)或手动拼接键值对 - 如果用了FormBuilder类或第三方表单库,确认它没内置Token机制,否则和ThinkPHP原生Token冲突
跨域或CLI环境无法使用Session Token
Token机制本质是服务端Session + 表单隐藏字段的配合。一旦请求不携带有效Session ID(如跨域未带credentials、CURL未设Cookie、命令行php think run),token()生成的值就无法与后端Session关联,校验必挂。
性能影响:每次token()调用会触发一次Session读写,高并发下可能成为瓶颈;兼容性上,ThinkPHP 5.1和6.0的token()参数签名不同(6.0支持name和reset参数),混用会出错。
- API接口不用这套——改用JWT或一次性签名参数,别硬套
token_check() - 测试时用
curl -b "PHPSESSID=xxx" -d "__token__=yyy"模拟,别直接跑CLI命令 - 升级ThinkPHP版本时,检查
token()调用是否加了废弃参数,例如5.1不支持token('name', true)这种写法
真正麻烦的是Token生命周期和业务状态的耦合——比如用户填完表单去付款,付款回调回来再提交,这时候Session可能已过期,Token自然无效。这种场景得换思路,不能只靠Session存Token。



















