当前位置: 首页 > news >正文

Hyperf方案 API签名验证

<?php/** * 案例056:API签名验证 * 说明:HMAC-SHA256签名防请求篡改,适合开放API对接第三方 * 需要安装的包:hyperf/http-server */declare(strict_types=1);namespaceApp\Middleware;usePsr\Http\Message\ResponseInterface;usePsr\Http\Message\ServerRequestInterface;usePsr\Http\Server\MiddlewareInterface;usePsr\Http\Server\RequestHandlerInterface;/** * API签名验证中间件 * * 签名流程: * 1. 客户端准备参数:app_id + timestamp + nonce + 业务参数 * 2. 把所有参数排序后拼成字符串 * 3. 用HMAC-SHA256和AppSecret对字符串签名,得到sign * 4. 请求时携带app_id + timestamp + nonce + sign * 5. 服务端用相同的方法重新计算sign,对比是否一致 * * 防重放攻击:timestamp超过5分钟或nonce已使用过,拒绝请求 */classSignatureMiddlewareimplementsMiddlewareInterface{privateint$timestampTolerance=300;// 时间戳容忍5分钟偏差,防重放publicfunctionprocess(ServerRequestInterface$request,RequestHandlerInterface$handler):ResponseInterface{// 从Header里取鉴权信息(也可以放在Query参数里)$appId=$request->getHeaderLine('X-App-Id');$timestamp=$request->getHeaderLine('X-Timestamp');$nonce=$request->getHeaderLine('X-Nonce');// 随机串,防重放$sign=$request->getHeaderLine('X-Signature');// 基本参数检查if(empty($appId)||empty($timestamp)||empty($nonce)||empty($sign)){return$this->error(400,'缺少签名参数');}// 时间戳范围检查:超过5分钟的请求拒绝(防重放)if(abs(time()-(int)$timestamp)>$this->timestampTolerance){return$this->error(400,'请求已过期,请检查系统时间');}// 检查nonce是否已使用过(防重放攻击,同一nonce只能用一次)if($this->isNonceUsed($appId,$nonce)){return$this->error(400,'nonce已使用,疑似重放攻击');}// 根据appId查询对应的AppSecret$appSecret=$this->getAppSecret($appId);if(!$appSecret){return$this->error(401,'AppId不存在');}// 获取请求参数,GET用query,POST用body$params=array_merge($request->getQueryParams(),(array)$request->getParsedBody());// 重新计算签名$expectedSign=$this->generateSign($appId,$timestamp,$nonce,$params,$appSecret);// 用hash_equals比较,防止时序攻击if(!hash_equals($expectedSign,$sign)){return$this->error(401,'签名验证失败');}// 签名验证通过,记录nonce防重放,有效期和时间戳容忍时间一样$this->markNonceUsed($appId,$nonce);return$handler->handle($request);}/** * 生成签名 * 算法:参数字典排序 -> 拼字符串 -> HMAC-SHA256 */publicstaticfunctiongenerateSign(string$appId,string$timestamp,string$nonce,array$params,string$appSecret):string{// 系统参数也加入签名,防止这些参数被篡改$allParams=array_merge($params,['app_id'=>$appId,'timestamp'=>$timestamp,'nonce'=>$nonce,]);// 去掉空值参数(没值的不参与签名)$allParams=array_filter($allParams,fn($v)=>$v!==''&&$v!==null);// 字典序排序(按key的ASCII码升序)ksort($allParams);// 拼成 key=value&key=value 格式$queryString=http_build_query($allParams,'','&');// HMAC-SHA256签名,用AppSecret作为密钥returnhash_hmac('sha256',$queryString,$appSecret);}privatefunctiongetAppSecret(string$appId):?string{// 实际从数据库或配置中心取$apps=['app_001'=>'secret_key_for_app_001_change_this','app_002'=>'secret_key_for_app_002_change_this',];return$apps[$appId]??null;}privatefunctionisNonceUsed(string$appId,string$nonce):bool{$redis=\Hyperf\Utils\ApplicationContext::getContainer()->get(\Hyperf\Redis\Redis::class);return(bool)$redis->exists("api_nonce:{$appId}:{$nonce}");}privatefunctionmarkNonceUsed(string$appId,string$nonce):void{$redis=\Hyperf\Utils\ApplicationContext::getContainer()->get(\Hyperf\Redis\Redis::class);// nonce记录的过期时间要大于时间戳容忍时间,确保在容忍期内不会重复使用$redis->setex("api_nonce:{$appId}:{$nonce}",$this->timestampTolerance+60,'1');}privatefunctionerror(int$status,string$message):ResponseInterface{$response=\Hyperf\Utils\ApplicationContext::getContainer()->get(\Hyperf\HttpServer\Contract\ResponseInterface::class);return$response->json(['code'=>$status,'message'=>$message])->withStatus($status);}}/** * 客户端签名示例(给接入方参考) * 这是PHP版本的,其他语言逻辑一样 */classApiClient{publicfunction__construct(privatestring$appId,privatestring$appSecret,privatestring$baseUrl,){}publicfunctionrequest(string$method,string$path,array$params=[]):array{$timestamp=(string)time();$nonce=bin2hex(random_bytes(8));// 16位随机串// 计算签名$sign=SignatureMiddleware::generateSign($this->appId,$timestamp,$nonce,$params,$this->appSecret);// 发请求,把签名信息放在请求头里$client=new\GuzzleHttp\Client(['base_uri'=>$this->baseUrl]);$resp=$client->request($method,$path,['headers'=>['X-App-Id'=>$this->appId,'X-Timestamp'=>$timestamp,'X-Nonce'=>$nonce,'X-Signature'=>$sign,'Content-Type'=>'application/json',],'json'=>$params,]);returnjson_decode($resp->getBody()->getContents(),true);}}
http://www.jsqmd.com/news/620279/

相关文章:

  • 第15届省赛蓝桥杯大赛C/C++大学B组
  • C++跨平台性能监控实战:构建CPU、GPU、磁盘与网络一体化探针
  • nav标签适用场景是什么_导航栏语义化写法【方法】
  • 部署成本降60%,响应提速10倍:镜像视界AI视频孪生的工程化价值
  • mysql事务隔离级别切换注意事项_如何保证系统平滑过渡
  • 【传统图像分割算法】- 图像分割之自适应阈值(Adaptive Thresholding)完全解析
  • 嵌入式Linux实战:手把手教你为EC20 4G模块编译GobiNet驱动(附完整Makefile配置)
  • HY-Motion 1.0开源大模型部署教程:支持企业级3D数字人动作生成生产环境
  • 2279 上市公司跨国供应链【存续力】(Sustainability) 指标(2004.07-2026.02)
  • C++ vs .NET 数组原地反转实测:小数组 C++ 碾压,大数组 .NET 反杀?好
  • 蛋白靶点CD49e(整合素α5):细胞黏附机制与抗体药物研发技术解析
  • 《算法题讲解指南:动态规划算法--回文串问题》--35.回文子串,36. 最长回文子串,37.分割回文串 IV,38.分割回文串 II,39.最长回文子序列,40.让字符串成为回文串的最少插入次数
  • 一个拉胯的分库分表方案有多绝望?整个部门都在救火!
  • 茉莉花插件:5步掌握Zotero中文文献管理终极技巧
  • 【GUI-Agent】阶跃星辰 GUI-MCP 解读---()---GUI-MCP 整体架构炔
  • 【web服务】web服务之nginx详细配置上
  • IPD实战指南:如何运用SPAN工具精准定位高潜力市场并优化产品战略布局
  • 从“记录监控”到“空间决策”:镜像视界AI重新定义视频孪生
  • LLM推理微服务基准测试全链路指南,从Prompt扰动控制到P99延迟归因分析
  • Java项目Loom迁移避坑手册(2024生产环境血泪总结)
  • 从“虚短虚断”到闭环增益:深度解析理想运放负反馈放大电路
  • ruoyi前后端分离版本
  • ESP8266智能配网实践:从SmartConfig到密码持久化存储
  • Cadence 17.2 实战指南:从零开始创建电阻、电容与LED的原理图Symbol库
  • 5个实战项目带你玩转知识追踪数据集(附ASSISTments2015完整分析代码)
  • RAG从入门到精通:如何解决检索语义不匹配(附携程面经),看这篇就够了!
  • 密码学的数学基础3-浮点数在计算机中的的实现
  • OpenClaw监控面板:可视化SecGPT-14B安全任务执行状态
  • ONNX模型可视化指南:用Netron+C#实现模型结构解析与输入输出验证
  • 《OpenClaw (Docker手工部署版) 终极避坑与实战指南》橙