发布于2026-07-14 阅读(0)
扫一扫,手机访问
签名校验必须在中间件中实现,不能放控制器里——因为中间件是TP8请求生命周期里唯一能统一拦截、不侵入业务逻辑、且确保所有接口被覆盖的环节。控制器校验很容易重复或遗漏,一旦漏掉某个接口,重放攻击的风险就来了。

TP8 的请求生命周期里,中间件是唯一一个能统一拦截所有接口、同时不侵入业务逻辑的位置。要是把校验逻辑塞进控制器,要么每个方法里都得重复写 checkSign(),要么某个新接口忘了加——重放攻击就专挑漏掉的地方下手。
推荐新建中间件 app/middleware/ApiSignatureCheck.php,然后在 app/middleware.php 中全局注册(或者按路由分组绑定):
return [
'api' => [
app\middleware\ApiSignatureCheck::class,
],
];
注意中间件的顺序很关键:它必须在 ValidatePostSize 之后、业务控制器之前执行,否则可能读不到完整的请求体。
TP8 默认用 input() 获取参数,但这个方法对 GET/POST/JSON 混合请求的行为不一致。必须统一用 $request->param()(合并后的结果),再手动提取 sign、timestamp、nonce,最后构造待签名字符串。
几个常见的坑:
ksort()),否则客户端和服务端算出来的签名永远对不上。null、0 怎么处理?建议统一转成空字符串 '',避免 PHP 自动类型转换干扰哈希结果。rawurlencode() 而不是 urlencode(),后者会把空格转成 +,而标准签名要求空格转成 %20。核心逻辑片段示例:
$params = $request->param();
unset($params['sign']);
ksort($params);
$toSign = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$expectedSign = hash_hmac('sha256', $toSign, config('api.sign_key'));
只校验 timestamp 是否在 ±5 分钟以内是不够的——攻击者可以截获一次合法请求,反复重放同一套 timestamp+nonce+sign。必须把 nonce 存起来做去重。
推荐做法:
timestamp 校验:当前时间与请求时间差的绝对值 ≤ 300 秒,超过直接拒绝。nonce 校验:用 Redis 存 setex api:nonce:{$nonce} 300 1,如果设置失败(返回 0)说明已经存在,直接拒掉。Redis key 命名建议带上前缀,过期时间与 timestamp 窗口对齐,避免残留。同时 nonce 长度至少 16 位随机字符串(用 bin2hex(random_bytes(16))),防止被暴力枚举。
签名失败时,别返回 "sign error: invalid timestamp" 这种提示——攻击者会通过响应差异来判断你校验了哪一步。所有失败都用同一个错误码和消息,比如 401 Unauthorized + {"code":401,"msg":"Unauthorized"}。
另外,TP8 的异常处理机制默认会输出调试信息,线上环境务必确认 app_debug 为 false,并且在中间件里用 throw new HttpException(401, 'Unauthorized') 而不是 exit/json,确保走统一的异常渲染流程。
还有一个容易被忽略的点:如果接口支持 CORS,签名中间件必须在 Cors 中间件之后运行,否则预检请求(OPTIONS)会被拦住。但 OPTIONS 本身不应该做签名校验,所以要在中间件里加一句:if ($request->method() === 'OPTIONS') return $next($request);。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8