发布于2026-07-04 阅读(0)
扫一扫,手机访问
签名验证必须放在中间件里,绝不能放到控制器中去执行——原因很简单:控制器已经进入业务逻辑层,路由匹配和参数解析都完成了,恶意请求可能已经触发了日志或数据库操作。而中间件才是请求进入业务前的最后一道可控拦截点。

ThinkPHP 的 API 签名验证必须放在路由调度之前,否则等到了控制器再动手,已经晚了——攻击者完全可能绕过业务逻辑,直接触发敏感操作。推荐的做法是在 app/middleware/SignVerifyMiddleware.php 中实现,然后全局注册或按路由组挂载。
需要特别注意的是:别把签名验证逻辑塞进 app/common.php 或者控制器的 initialize() 方法里。前者根本没有请求上下文,后者已经进入业务层,防御效果大打折扣。
app/middleware.php 中加入 'app\middleware\SignVerifyMiddleware'->middleware(SignVerifyMiddleware::class)通常的做法是:将非空参数(包括 timestamp、nonce、appid)拼接起来,按字典序排序,然后用密钥做 HMAC-SHA256 加密。但这里真正的难点不是“怎么算”,而是“哪些字段必须参与且不能被绕过”。
常见错误是只对 GET 参数签名,却忽略了 POST body;或者压根没校验 timestamp 的有效期(建议不超过 300 秒),导致重放攻击依然有效。
timestamp 必须是当前 Unix 时间戳,服务端需要校验与本地时间差 ≤ 300 秒nonce 需要存入 Redis 并设置 TTL,防止重复使用,key 可设计为 "nonce:{$appid}:{$nonce}"Content-Type: application/json 下的原始 bodyappid=abc&nonce=xyz×tamp=1717023456&v=1.0(注意无空格、无编码、小写键名)签名通常放在 Header(如 X-Signature)或 query string(如 ?sign=xxx),但 Header 更规范。千万注意:不要从 $_GET 或 $request->param() 直接取 sign 字段——它很可能被业务参数污染,导致校验结果不可靠。
ThinkPHP 的 $request->header('X-Signature') 是唯一可信的入口,而且必须在中间件早期就提取出来,避免后续逻辑修改 header 或 request 对象。
$request->header('X-Signature') 获取签名,然后手动解析参数(不要用 $request->param())$request->getContent() 读取原始 body,再通过 json_decode($raw, true) 合并到参数数组hash_equals($expected, $provided) 防止时序攻击,ThinkPHP 8.x 已内置,低版本需自行引入config('api.sign_key'),生产环境应由 Env 注入返回 401 Unauthorized 却没有说明原因,这是签名中间件最让人头疼的体验。根本原因在于中间件里抛异常后没有携带上下文信息,而 ThinkPHP 默认会吞掉 detail。
解决方案是:在中间件中捕获异常时,主动记录日志并返回结构化的错误响应,而不是依赖默认的异常页。
Log::error("Sign verify failed: {$msg}", ['raw' => $request->param(), 'header' => $request->header()])json(['code' => 401, 'msg' => 'Invalid signature'], 401)curl -H "X-Signature: xxx" "http://api.test/v1/user?appid=test×tamp=1717023456&nonce=abc" 手动构造最直观app_debug = true,否则 Log::error 可能不写磁盘必须警惕的是:签名验证真正的复杂度并不在算法本身,而在于参数来源的完整性、时间同步的容忍度、以及密钥轮换时的灰度兼容——这些细节往往要等到上线后才会暴露出来。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8