TP6 怎么配置表单令牌验证防止 CSRF 攻击

文章导读
ThinkPHP 6 的表单令牌验证并非默认全局开启,需要确保 Session 机制正常,并在模板输出令牌字段,同时通过中间件或路由显式启用校验逻辑。
📋 目录
  1. A 前置环境检查
  2. B 启用令牌验证
  3. C 模板输出令牌
  4. D AJAX 请求处理
  5. E 验证与异常捕获
  6. F 常见坑与排查
A A

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 共享,否则生成和校验不在同一台机器会导致验证失败。

TP6 怎么配置表单令牌验证防止 CSRF 攻击

启用令牌验证

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 方法。

TP6 怎么配置表单令牌验证防止 CSRF 攻击

模板输出令牌

在 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 标签

TP6 怎么配置表单令牌验证防止 CSRF 攻击
<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
    })
});

验证与异常捕获

配置完成后,可通过以下方式验证是否生效:

  1. 正常提交:表单能正常提交并执行业务逻辑。
  2. 失效测试:复制表单 HTML 到新页面提交,或禁用浏览器 Cookie 后提交,服务端应抛出异常。
  3. 异常捕获:令牌校验失败会抛出 think\exception\ValidateException,可在全局异常处理或控制器中捕获。
try {
    // 业务逻辑
} catch (\\think\\exception\\ValidateException $e) {
    // 捕获令牌验证失败
    return json(['code' => 403, 'msg' => '令牌无效,请刷新页面重试']);
}

常见坑与排查

  • 多标签页失效:ThinkPHP 默认每次渲染{:token()} 会刷新 Session 中的令牌,旧页面表单提交会失败。这是安全机制,若需支持多标签,需修改令牌生成逻辑为不刷新 Session。
  • 模板缓存问题:若开启模板缓存,令牌值可能被缓存导致每次提交相同值。确保令牌部分代码不被缓存或使用动态生成。
  • 输入流被消耗:某些中间件提前读取了 php://input 导致 POST 数据丢失,令牌字段无法获取。检查中间件执行顺序。
  • 负载均衡 Session 不同步:多服务器部署时,若 Session 未共享(如未存 Redis),生成和校验不在同一台机器会失败。