ThinkPHP 6 的表单令牌验证并非默认全局开启,需要确保 Session 机制正常,并在模板输出令牌字段,同时通过中间件或路由显式启用校验逻辑。
核心结论:TP6 防 CSRF 依赖“模板生成令牌 + 后端校验令牌”,必须手动配置中间件或路由才能生效。
- 前置条件:确认 config/session.php 中 Session 驱动未禁用,否则令牌无法存储。
- 关键配置:表单内使用{:token()} 输出隐藏域,全局或路由中间件添加 CheckToken。
- 验证标准:提交表单后若无 think\exception\ValidateException 异常且业务正常执行,即表示生效。
前置环境检查
令牌机制依赖 Session 存储随机值,首先确保 Session 配置正确。检查 config/session.php 文件,确保 type 配置项未设置为 disabled。
// config/session.php
return [
// 驱动类型,可选 file, redis, memcache 等,不可为 disabled
'type' => 'file',
// 其他配置...
];
如果使用 Redis 存储 Session,需确保多服务器部署时 Session 共享,否则生成和校验不在同一台机器会导致验证失败。
启用令牌验证
ThinkPHP 6 推荐通过中间件方式开启校验,兼容性优于路由链式调用。
方案一:全局中间件(推荐)
在 config/middleware.php 中注册 CheckToken 中间件,对所有请求生效。
// config/middleware.php
return [
// 全局中间件定义
'\\think\\middleware\\CheckToken',
];
方案二:路由中间件
仅对特定路由生效,避免对 GET 请求或非表单接口造成干扰。
// route/app.php
use think\\facade\\Route;
use think\\middleware\\CheckToken;
Route::post('save', 'controller/Save')->middleware([CheckToken::class]);
注意:部分旧版本支持 Route::post(...)->token() 链式调用,但通用性较差,建议优先使用 middleware 方法。
模板输出令牌
在 form 标签内部调用{:token()} 函数,框架会自动生成 name 为__token__的隐藏输入框。切勿手动硬编码 value 值。
<form method="post" action="{/url 'save'}">
{:token()}
<input type="text" name="username">
<button type="submit">提交</button>
</form>
AJAX 请求处理
AJAX 请求不会自动携带表单隐藏域,需通过 JS 获取令牌值并放入请求数据中。默认 CheckToken 中间件主要校验 POST 数据,建议将令牌放入 data payload 而非 Header。
1. 页面输出令牌到 Meta 标签
<head>
<meta name="csrf-token" content="{:token()}">
</head>
2. JS 获取并发送(jQuery 示例)
var token = $('meta[name="csrf-token"]').attr('content');
$.ajax({
url: '/save',
type: 'POST',
data: {
username: 'test',
__token__: token // 关键字段,名称必须与后端配置一致
},
success: function(res) {
console.log(res);
}
});
3. 原生 JS 示例
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'test',
__token__: token
})
});
验证与异常捕获
配置完成后,可通过以下方式验证是否生效:
- 正常提交:表单能正常提交并执行业务逻辑。
- 失效测试:复制表单 HTML 到新页面提交,或禁用浏览器 Cookie 后提交,服务端应抛出异常。
- 异常捕获:令牌校验失败会抛出 think\exception\ValidateException,可在全局异常处理或控制器中捕获。
try {
// 业务逻辑
} catch (\\think\\exception\\ValidateException $e) {
// 捕获令牌验证失败
return json(['code' => 403, 'msg' => '令牌无效,请刷新页面重试']);
}
常见坑与排查
- 多标签页失效:ThinkPHP 默认每次渲染{:token()} 会刷新 Session 中的令牌,旧页面表单提交会失败。这是安全机制,若需支持多标签,需修改令牌生成逻辑为不刷新 Session。
- 模板缓存问题:若开启模板缓存,令牌值可能被缓存导致每次提交相同值。确保令牌部分代码不被缓存或使用动态生成。
- 输入流被消耗:某些中间件提前读取了 php://input 导致 POST 数据丢失,令牌字段无法获取。检查中间件执行顺序。
- 负载均衡 Session 不同步:多服务器部署时,若 Session 未共享(如未存 Redis),生成和校验不在同一台机器会失败。