必须在 validate 失败后显式记录完整请求上下文,推荐通过中间件捕获 ValidateException,记录 ip、method、url、controller、action、param(JSON 编码)、create_time 等字段,并同步写入数据库而非仅日志。

ThinkPHP 的 validate 方法默认不记录非法参数
直接调用 validate 做校验时,失败只返回 false 或抛异常,框架本身不会自动落库、打日志或触发审计动作。想做合法性审计,必须自己插入手动记录逻辑。
常见错误现象:validate 返回 false 后程序直接返回错误响应,攻击者反复发畸形参数(如 id=1%27%20OR%201=1),后台毫无痕迹可查。
- 务必在
validate返回false后,显式调用日志记录或入库操作 - 不要依赖
Validate::failException(true)—— 它只抛异常,不帮你记东西 - 验证失败的原始数据(
$this->request->param())要立刻保存,别等进业务逻辑后再取,可能已被过滤或覆盖
如何在 validate 失败时记录完整请求上下文
关键不是“能不能记”,而是“记哪些字段才对审计有用”。只记 ip 和 url 不够,SQL注入、越权类攻击需要看到原始参数值和控制器动作。
推荐记录字段:ip、method、url、controller、action、param(JSON 编码)、create_time。
立即学习“PHP免费学习笔记(深入)”;
-
param一定要用json_encode($this->request->param(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),避免日志解析失败 - 别用
$this->request->only([...])过滤后再记 —— 审计要的是“输入原貌”,不是你期望的字段子集 - 记录动作建议放在基类控制器的
initialize后置钩子或中间件里,统一拦截所有验证失败点
中间件中捕获 ValidateException 并审计的实操写法
比在每个控制器里写重复逻辑更可靠。ThinkPHP 6+ 的 ValidateException 是唯一明确标识“校验失败”的异常类型,其他异常(如数据库异常)不该混进来。
<?php
namespace app\middleware;
use think\exception\ValidateException;
use think\facade\Log;
class AuditValidateFail
{
public function handle($request, \Closure $next)
{
try {
return $next($request);
} catch (ValidateException $e) {
Log::channel('audit')->warning('validate_fail', [
'ip' => $request->ip(),
'url' => $request->url(true),
'method' => $request->method(),
'param' => json_encode($request->param(), JSON_UNESCAPED_UNICODE),
'rule' => $e->getRule() ?: '',
'msg' => $e->getMessage(),
]);
throw $e;
}
}
}
- 注意:必须用
Log::channel('audit')单独配置通道,避免和业务日志混在一起,方便后续用 ELK 或 grep 筛选 -
$e->getRule()是 ThinkPHP 6.1+ 新增方法,低版本需从异常消息里正则提取规则名,不建议 - 不要在中间件里尝试“修复”参数或重试验证 —— 审计的前提是承认输入非法,不是补救
为什么不能只靠日志,还得加数据库持久化
线上出问题时,日志轮转快、检索慢、权限分散;而审计数据要支持回溯、聚合统计(比如某 IP 一小时触发 200 次 id 非数字)、对接 WAF 规则。纯文件日志扛不住。
- 建表字段至少含:
id、ip、url、param_json(TEXT)、rule_name、created_at - 别用
insertAll批量写入审计日志 —— 高并发下容易锁表,单条insert更稳 - 如果用了 Redis 缓存,切勿把审计数据丢进缓存再异步落库 —— 掉电或崩溃会导致丢失,审计数据必须同步写磁盘
真正难的不是记下来,是区分“误操作”和“探测行为”。同一个 id=-1 出现在用户注册页是误填,在订单详情页就是越权试探 —— 上下文决定是否告警,而不是参数本身长什么样。


















