签名验证中间件应注册在laravel的app/http/kernel.php的$routemiddleware数组中,绑定易记别名如'sign.verify',不可含点号或空格;必须显式注册才生效,不注册等于未定义。

签名验证中间件该放在哪里注册
中间件必须显式注册才能生效,Laravel 不会自动扫描 app/Http/Middleware 下的类。不注册就等于没写。
推荐注册在 app/Http/Kernel.php 的 $routeMiddleware 数组里,用键值对方式绑定一个易记的别名:
protected $routeMiddleware = [
// ...
'sign.verify' => \App\Http\Middleware\VerifyRequestSignature::class,
];
别名不能含点号(.)或空格,否则路由中调用会报错;也不建议用 verify.sign 这种反向命名,容易和 Laravel 自带中间件混淆。
如何从请求中提取签名、时间戳和随机串
签名验证依赖三个关键字段:签名值(sign)、时间戳(timestamp)、随机串(nonce)。它们通常放在 query string 或 request body 中,但必须统一约定位置,否则前端和后端对不上。
常见错误是只支持 GET 参数,结果 POST JSON 请求失败——因为 $request->query() 拿不到 body 里的字段。
稳妥做法是按优先级合并提取:
- 先尝试从
$request->header('X-Sign')、$request->header('X-Timestamp')、$request->header('X-Nonce')读(推荐,更干净) - 查不到再 fallback 到
$request->query()和$request->post()合并后的数组(注意:JSON 请求需用$request->json()->all()) - 任意一个缺失,直接
return response()->json(['message' => 'Missing required fields'], 400);
别忘了校验 timestamp 是否为数字且在合理窗口内(比如 ±5 分钟),避免重放攻击。
签名生成逻辑必须前后端完全一致
服务端重新计算签名时,拼接顺序、编码方式、哈希算法必须和前端一模一样,差一个空格或一次 URL decode 都会导致验证失败。
典型签名规则示例(以 HMAC-SHA256 为例):
- 取所有非空请求参数(包括
timestamp、nonce,排除sign) - 按 key 字典序排序,拼成
key1=value1&key2=value2形式(value 必须rawurlencode,不是urlencode) - 拼上密钥(
$secret),计算hash_hmac('sha256', $string, $secret) - 最终比对的是十六进制小写字符串(
strtolower(bin2hex(...)))
调试时可临时把服务端算出的待签字符串和签名打印出来,和前端日志对照。别依赖“应该没错”——90% 的签名失败源于拼接逻辑不一致。
中间件里怎么处理验证失败
验证失败不能抛异常(除非你全局捕获并转成 JSON 响应),否则会触发 Laravel 默认的 500 页面或调试堆栈,暴露敏感信息。
正确做法是直接返回标准响应:
if (! $this->isValidSignature($request)) {
return response()->json([
'message' => 'Invalid or expired signature',
'code' => 401
], 401);
}
注意两点:
- 状态码建议用
401 Unauthorized(签名无效)或403 Forbidden(权限不足),不要用400 Bad Request模糊语义 - 别在响应里泄露失败原因细节,例如“timestamp too old by 32s”——这会给攻击者提供时间偏移参考
另外,中间件里别做耗时操作(如查数据库验证 nonce 是否已用过),高频接口要加 Redis 缓存去重,否则签名验证本身就成了性能瓶颈。











