ThinkPHP8 授权签名验证必须在中间件中实现,通过统一拦截请求、严格按规则构造签名原文(URL路径+排序编码参数+sign_version+密钥)、比对客户端签名来保障安全,禁止在控制器中校验。

ThinkPHP8 添加授权签名验证,核心是在中间件中统一拦截请求、构造签名原文、比对客户端签名,不能放到控制器里做——否则业务逻辑可能已被执行,验签就失去意义。
签名验证必须用中间件实现
验签逻辑要放在请求最早期阶段,确保未通过验证的请求不进入路由匹配和控制器执行:
- 在
app/middleware.php中注册自定义中间件,例如App\Middleware\SignCheck::class - 中间件中调用
$request->isGet() ? $request->get() : $this->getRawParams($request)获取参数,避免$request->param()自动解码导致与客户端签名不一致 - 对 JSON 请求体,需先判断
Content-Type是否为application/json,再用$request->getContent()取原始 body 并参与拼接
签名原文拼接规则(关键细节)
服务端必须严格按客户端方式构造签名原文,否则 90% 的失败源于此处不一致:
- 取接口路径:
$request->url(true)(如/api/v1/order),不含域名和查询参数 - 合并 GET 和 POST 参数(不包含
sign字段),ksort()按 key 字典序排序 - 对每个键值对,分别用
rawurlencode($k)和rawurlencode($v)编码,拼成k1=v1&k2=v2格式 - 追加
sign_version=1(如有版本控制)和密钥:rawurlencode($secretKey) - 最终用
hash_hmac('sha256', $string, $secretKey)或md5()生成签名(推荐 SHA256)
客户端与服务端协同要点
前后端必须约定一致,否则签名必然失败:
立即学习“PHP免费学习笔记(深入)”;
- 时间戳
timestamp必须传,服务端校验是否超时(如 ±5 分钟),防止重放攻击 - 随机数
nonce需唯一且短时效,建议结合时间戳+微秒+随机字符串生成 - 中文参数前后端都用 UTF-8,PHP 内部设
mb_internal_encoding('UTF-8') - 所有参数值提前
trim()并清除隐藏字符(\r、\n、BOM 等) - 调试时可加
?debug_sign=1参数,让中间件输出服务端构造的签名原文,方便比对
安全增强建议
基础验签只是起点,生产环境还需补充防护:
- 密钥不硬编码,存于
runtime/keys/非 Web 可访问目录,用容器单例缓存加载结果 - 支持 RSA 验签时,公钥必须为 PKCS#8 格式(
-----BEGIN PUBLIC KEY-----),PHP 8.0+ 不认 PKCS#1 - 高频异常签名请求(如 1 分钟内失败超 5 次)可记录 IP 并临时限流
- 敏感接口(如支付、用户资料修改)强制要求签名,非敏感接口可选配



















