Webman接口防爬必须在onRequest阶段完成动态验签+行为层拦截,因onRequest最早可获取完整请求数据,比路由匹配更早,能有效防止绕过路由或伪造session的攻击。

User-Agent 校验拦不住真实爬虫,robots.txt 对恶意接口调用完全无效——Webman 接口防爬必须靠动态验签 + 行为层拦截,不能只拼 header 或写死规则。
Webman 中签名验证必须在 onRequest 阶段完成
Webman 的生命周期里,onRequest 是最早能拿到完整请求数据(method、path、headers、raw body)的钩子,比路由匹配还早。把验签逻辑塞进控制器或中间件(比如 app/middleware/Auth.php)就晚了:攻击者可绕过路由直接打到服务层,或伪造已登录 session 触发业务逻辑。
正确做法是修改 start.php,在 Worker::onRequest 回调里统一拦截:
Worker::onRequest = function ($request, $response) {
$method = $request->method();
$path = $request->path();
$timestamp = $request->header('X-Timestamp');
$signature = $request->header('X-Signature');
<pre class="brush:php;toolbar:false;">// 1. 时间戳必须在 5 分钟内
if (!$timestamp || abs(time() - (int)$timestamp) > 300) {
return $response->withStatus(401)->end('Invalid timestamp');
}
// 2. 拼原始签名串:method + path + sorted query + sorted json body + timestamp
$query = http_build_query($request->get(), '', '&', PHP_QUERY_RFC3986);
$body = $request->rawBody();
$json = $body && json_decode($body, true) ? json_encode(array_filter(json_decode($body, true), function($v) { return $v !== null && $v !== ''; }), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : '';
$sign_string = $method . $path . $query . $json . $timestamp;
$expected = base64_encode(hash_hmac('sha256', $sign_string, config('app.api_secret'), true));
if (!hash_equals($expected, $signature)) {
return $response->withStatus(401)->end('Invalid signature');
}};
- 别用
$_GET/$_POST拼参——它们已被 PHP 自动 urldecode,和客户端原始签名不一致 -
json_encode必须加JSON_UNESCAPED_UNICODE,否则中文字段签名会错 -
hash_equals()防时序攻击,不用=== -
config('app.api_secret')应从环境变量读取,绝不能硬编码
为什么不能只依赖 User-Agent 或 Referer
Webman 是常驻内存的长连接框架,User-Agent 和 Referer 这类 header 完全可被任意篡改,连 curl 都能伪造得一模一样。更危险的是:合法 UA(如 Chrome 最新字符串)+ 空 Cookie + 空 Referer + 无 X-Requested-With,大概率就是 headless 爬虫——但你用 stripos($ua, 'Chrome') 会直接放行。
真正该做的不是“判断是不是浏览器”,而是“判断是不是你自己的前端发的请求”:
- 前端每次请求前,用固定密钥 + 当前时间戳 + 随机 nonce 生成一次性的
X-Signature - 后端只认这个签名,不关心 UA 是什么;微信小程序、App、H5 全部走同一套逻辑
- 禁用所有未带
X-Timestamp和X-Signature的请求,连 OPTIONS 预检都拦掉
Redis 滑动窗口限流必须绑定签名上下文
单纯按 IP 限流在 Webman 下容易误杀:NAT 网关后一堆用户共用一个出口 IP;而只按接口路径限流又太粗——/api/v1/user/info 被刷爆,不影响 /api/v1/order/create 才合理。
推荐用签名中的关键字段组合做限流 key:
// 示例:按「用户ID + 接口路径」每分钟最多 30 次
$user_id = $request->header('X-User-Id') ?: 'guest';
$key = "rate:{$user_id}:{$path}:" . date('Y-m-d-H-i');
$redis = \support\Redis::getInstance();
if ($redis->incr($key) > 30) {
$redis->expire($key, 60);
return $response->withStatus(429)->end('Too many requests');
}
- 不要用
$_SERVER['REMOTE_ADDR'],Webman 可能跑在反向代理后,需从X-Real-IP或X-Forwarded-For提取(且要校验可信代理列表) - key 中必须含时间片段(如
Y-m-d-H-i),避免 Redis 内存无限增长 - 限流失败时别返回详细错误,防止暴露防护策略
Webman 的 onMessage 和 onClose 不适合做防爬主逻辑
WebSocket 场景下,有人想在 onMessage 里做签名校验——这会导致两个问题:一是握手阶段没验,攻击者可先建连再发恶意帧;二是 WebSocket 帧本身不带标准 HTTP header,X-Signature 得手动塞进 payload,增加前端复杂度。
正确姿势是:HTTP 握手阶段(即 onRequest)就完成验签和限流,拒绝非法连接;WebSocket 连接建立后,只校验 session 或 token,不再重复验签。
最易被忽略的一点:Webman 默认启用 buffer_output,若验签失败后直接 return $response->end(),可能因缓冲未刷新导致客户端收不到响应。务必确认 config/app.php 中 'buffer_output' => false 已关闭,或显式调用 $response->send()。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











