ThinkPHP 的 _token 由会话ID、时间戳、随机字符串等通过md5/sha1混合哈希生成,每次刷新页面更新;验证时服务端重算比对,超时(默认60秒)或session异常即失败。

ThinkPHP 的 _token 是怎么生成和验证的
ThinkPHP 默认开启表单令牌(CSRF Token)机制,_token 字段本质是服务端生成的一次性哈希值,绑定当前会话(session_id)、时间戳、随机字符串及可能的额外参数。它不是固定密钥加密,而是通过 md5 或 sha1 混合计算得出,每次刷新页面都会变化。
关键点在于:验证时框架会用相同逻辑重算一次 _token,再与表单提交的值比对;若不一致或超时(默认 60 秒),就抛出 TokenErrorException。
-
_token默认有效期为 60 秒,由配置项token_expire控制,过期后即使 session 有效也会校验失败 - 生成逻辑在
think\template\driver\File::token()和think\Validate::token()中调用,底层依赖think\helper\Str::random()生成随机因子 - 如果关闭了 session(比如用 Redis 存储但配置错误),
_token会始终为空或无法匹配,导致“令牌无效”错误
如何在模板中正确输出并提交 _token
不能手动写死或拼接 _token 值,必须用 ThinkPHP 提供的助手函数或标签生成,否则服务端无法还原原始计算上下文。
推荐方式是使用 {:token()} 模板函数(V6.x)或 {__TOKEN__}(V5.x):
立即学习“PHP免费学习笔记(深入)”;
<form method="post">
{:token()}
<input type="text" name="title">
<button type="submit">提交</button>
</form>
- V6.x 中
{:token()}默认生成隐藏域<input type="hidden" name="_token" value="xxx">,值已自动 Base64 编码(防 HTML 特殊字符干扰) - 若使用 AJAX 提交,需先从页面 DOM 中读取
_token值,不能靠 JS 重新生成——因为缺少服务端 session 上下文 - 不要在多个表单共用同一个
_token输出结果,ThinkPHP 每次调用{:token()}都会生成新值,重复使用会导致后提交的表单失败
为什么验证总失败?常见校验中断原因
最常遇到的不是“没加 _token”,而是校验流程被意外绕过或破坏。
- 控制器方法没继承
think\Controller(V5)或没使用think\middleware\TokenCheck(V6),导致自动校验中间件未生效 - 路由定义为
get请求却接收post+_token,或用了allowCrossDomain但未配置Access-Control-Allow-Headers: X-Requested-With,引发预检失败从而丢弃 token 字段 - 表单 action 地址带 query 参数(如
?id=123),而验证时框架默认忽略 URL 参数参与 token 计算——但如果你自定义了token_on为true并启用了token_name,且参数参与了 hash,则前后不一致 - Nginx 或 CDN 缓存了含
_token的 HTML 页面,用户拿到的是过期 token,刷新页面也无效(需禁用 HTML 缓存或设置Cache-Control: no-cache)
自定义 _token 行为的风险点
可以通过配置修改 _token 名称、有效期、是否绑定 URL 参数等,但改动后必须同步确保前后端逻辑一致。
- 修改
token_name(如设为csrf_token)后,模板里仍用{:token()}即可,但 AJAX 手动提交时必须把字段名也改成新名称 - 设
token_on为false会全局禁用,不推荐;局部关闭应使用['token' => false]在验证规则中声明 - 延长
token_expire超过 300 秒需谨慎:token 值存储在 session 中,若 session 过期早于 token,校验仍失败;两者生命周期要对齐 - V6.1+ 支持
token_hash配置切换哈希算法(如sha256),但旧版本浏览器或某些代理可能截断长 token 值,建议保持默认md5兼容性优先
真正麻烦的从来不是生成或验证本身,而是 token 生命周期和服务端 session 状态、前端缓存策略、跨域配置之间的隐式耦合。一个环节没对齐,就表现为“明明写了 token 却提示无效”。



















