签名验证必须在中间件统一拦截,核心是时间戳、随机串、参数归一化、原始数据字节级拼接四点闭环,而非仅md5加盐;需排除sign等字段、严格utf-8编码、固定拼接顺序,并配套防重放与限流措施。

签名验证必须放在中间件里,不是“加不加盐”的问题,而是整个验签流程是否在请求入口就拦截。用 MD5 加盐只是基础手段,真正防篡改靠的是时间戳、随机串、参数归一化、原始数据拼接这四点闭环。
签名必须在中间件统一校验
控制器里做验签等于门锁装在卧室里——请求早已进到业务层,恶意参数可能已写库、发短信、扣库存。ThinkPHP 6+ 的中间件支持 beforeAction 钩子,所有带 sign 参数的请求都能被精准拦截。
- 中间件路径建议:
app/middleware/SignCheckMiddleware.php - 注册位置:在
app/middleware.php中全局或按路由组启用 - 失败响应要立即终止:
return json(['code'=>401,'msg'=>'Unauthorized'])->send(); exit;
原始数据必须字节级一致,别用 param() 取参
客户端用 UTF-8 拼出签名原文,服务端若用 $this->request->param() 取参,会自动 trim、urldecode、类型转换(比如把空格转成 +),导致签名比对失败。
- 表单请求(
application/x-www-form-urlencoded):用$this->request->get() + $this->request->post()合并,再ksort()排序 - JSON 请求(
application/json):先判断 header,再用$this->request->getContent()读原始 body,json_decode($raw, true)后排序 - 所有参数值统一做
trim()和mb_convert_encoding($val, 'UTF-8', 'UTF-8')强制编码 - 排除字段:
sign、signature、sign_type等签名相关参数必须提前 unset
签名原文拼接规则不能松动
MD5 不是随便拼个字符串再加盐就行。签名原文必须含动态因子 + 业务参数 + 私钥,且顺序固定、不可预测。
- 必含三要素:当前时间戳(
timestamp,精确到秒)、16位随机串(nonce)、服务端独有密钥(secret) - 业务参数处理:所有非排除字段按 key 字典序升序排列 →
http_build_query($params, '', '&', PHP_QUERY_RFC3986)拼接 - 完整签名原文:
拼接后的业务参数 & timestamp=xxx & nonce=yyy,最后追加secret - 推荐哈希方式:
strtoupper(md5($raw_string . $secret)),大小写需与客户端严格一致
防重放和防暴力的关键配套措施
只校验签名本身,等于没设防。时间窗口、随机串去重、限流缺一不可。
-
timestamp校验范围:仅接受当前服务端时间 ±300 秒内的请求,超时直接拒 -
nonce去重:存 Redis,key 为"nonce:{$app_id}:{$nonce}",TTL 设 300 秒 - 连续 5 次验签失败:按
app_id或 IP 记录 Redis 计数器,10 分钟内限制请求 - 错误提示统一:永远返回
{"code":401,"msg":"Unauthorized"},不暴露具体失败原因 - 日志脱敏:记录请求时过滤
password、token、id_card等敏感字段
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











