签名验证失败时,thinkphp 默认解析 json 请求导致原始 post body 无法获取;应立即读取 php://input、禁用自动解析,并用 hash_hmac('sha256') 配合标准化参数拼接验签。

签名验证失败时,think\facade\Request 获取不到原始 POST body?
ThinkPHP 默认会对 application/json 请求自动解析为数组,并清空原始 php://input 流。签名依赖原始请求体(如 JSON 字符串)计算,一旦被解析就无法还原,导致验签失败。
- 在中间件或控制器开头,用
file_get_contents('php://input')立即读取原始数据,且只读一次 - 禁用自动 JSON 解析:在
config/app.php中设'json_decode' => false,或在路由定义中加['json' => false] - 若必须用
$request->post(),需先缓存原始 body,再手动json_encode($request->post())—— 但注意键顺序、空格、浮点数精度等会导致哈希不一致
md5 和 hash_hmac 哪个更适合 API 签名?
md5 不安全,已不适用于签名场景;hash_hmac 是必须选择,它能防止长度扩展攻击,且支持密钥隔离。
- 推荐算法:
hash_hmac('sha256', $data, $secret_key),返回 64 字符十六进制字符串 - 密钥不能硬编码在代码里,应从配置或环境变量读取:
config('api.sign_key') - 签名原文拼接顺序必须严格统一:通常为
sort(参数键值对) → urldecode → key1=value1&key2=value2,不含空格和换行 - 特别注意:
timestamp参数必须校验时效性(如 ±300 秒),否则重放攻击可绕过签名
如何在 ThinkPHP 路由层统一拦截并验签?
把验签逻辑放在全局中间件最稳妥,避免每个接口重复写,也防止遗漏。
- 创建中间件:
php think make:middleware CheckApiSignature - 在
handle()中:提取sign、timestamp、nonce参数 → 校验时间戳 → 拼接待签名字符串 → 计算本地签名 → 对比 - 注意过滤系统参数:
sign、timestamp、nonce、version不参与签名拼接 - 返回错误时用
json(['code'=>401,'msg'=>'invalid sign'])并return $next($request)之前中断流程
前端传参含嵌套数组或特殊字符时,签名总不一致?
ThinkPHP 的 $request->param() 会递归合并 GET/POST,但签名要求「原始参数形态」——尤其是 PHP 对 foo[]=1&foo[]=2 和 foo[0]=1&foo[1]=2 解析结果相同,但原始字符串不同。
- 验签时一律使用
$request->get(false)和$request->post(false)获取原始未解码参数(保留%20等) - 对所有参数值执行
rawurlencode()再拼接,而非urlencode()(后者会把空格转成+) - 嵌套结构建议前端扁平化提交(如
user_name代替user[name]),或约定使用 JSON body +Content-Type: application/json,此时签名基于完整 JSON 字符串
php://input 被提前消耗。这些地方出错,连正确的密钥和算法也救不回来。大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











