评论

对wechatpay-php开发包APIv3强制验签逻辑进行优化设计及实现思路思考

引申自「关于v2接口中间件强行要求验证sign字段问题」, 当前已知在 APIv3 的三个接口上,有三种“特殊”逻辑,须对当前版本SDK进行迭代做特殊处理,以优化开发体验。

运行环境

wechatpay-php: ^1.4.4

需求

引申自关于v2接口中间件强行要求验证sign字段问题 #92, 当前已知在 APIv3 的三个接口上,有三种“特殊”逻辑,当前版本SDK并没有特殊处理,如下:

1. 国内商户客诉图片下载

URL: /v3/merchant-service/images/{slot}

接口本身返回的流stream,无验签逻辑;

2. 国内商户账单下载

URL: /v3/billdownload/file

接口本身返回的流stream,验签逻辑依赖前置请求账单的SHA1摘要,而前置接口的返回的SHA1值,已按标准RSA公钥逻辑验签;

3. 海外商户账单下载

URL: /hk/v3/statements

请求的返回值,签名含两种逻辑,标准RSA公钥验签逻辑<sup>注1</sup>及流stream SHA1摘要逻辑;

注1: 验签的是 SHA1 摘要的JSON表达式,而非流,与标准RSA公钥验签验证返回BODY不同;

分析

  1. 第1种情形,接口设计的比较另类另说(见 增加了两条APIv3的测试用例,示例说明图片下载及上传的特殊用法 #85 ),无验签逻辑需要客户端自动忽略;

  2. 第2种是两步请求分开验签,先RSASHA1,验签逻辑含上下文context语境,后置的客户端本地验签逻辑可循 以函数链的形式流式下载交易帐单 示例,在 ->then 链上处理,当前请求返回值无明确签名,客户端需要忽略验签;

  3. 第3种情形可以看作是第2种的特例,一步请求,接口即回吐了RSA签名又包含SHA1摘要,客户端设计可以仿第2种逻辑,仅对RSA签名部分进行验签,保证当前请求的返回值,是来自平台RSA私钥数据签名<sup>注2</sup>,SHA1摘要逻辑交由客户端本地在 ->then 链上自行比对摘要值;

注2: 按照规范,非对称加密的公钥是公开的,可任意发行的,而平台RSA私钥是私密数据,一次请求即含签名又含摘要,虽然任何人(持有效公钥)都可以验签,但RSA签名值本身是不可伪造,是安全的。

改进

按照上述分析,对前两种接口设计,当次请求/响应需要忽略RSA验签;对第三种逻辑,需要构造一个本地摘要值JSON串,做一次RSA公钥验签,验签若失败,则按当前处理逻辑抛\GuzzleHttp\Exception\RequestException异常;RSA验签成功则(忽略SHA1值比对逻辑)放行。

实现

\WeChatPay\ClientJsonTrait::assertSuccessfulResponse做逻辑补丁加强,代码类似如下:

diff --git a/src/ClientJsonTrait.php b/src/ClientJsonTrait.php
index 7957f21..af8d870 100644
--- a/src/ClientJsonTrait.php
+++ b/src/ClientJsonTrait.php
@@ -13,6 +13,9 @@ use function count;
 use function sprintf;
 use function array_key_exists;
 use function array_keys;
+use function in_array;
+use function strncasecmp;
+use function stripos;

 use GuzzleHttp\Client;
 use GuzzleHttp\Middleware;
@@ -88,6 +91,16 @@ trait ClientJsonTrait
     protected static function assertSuccessfulResponse(array &$certs): callable
     {
         return static function (ResponseInterface $response, RequestInterface $request) use(&$certs): ResponseInterface {
+            if (
+                in_array($url = $request->getRequestTarget(), [/*mainland*/ '/v3/billdownload/file', /*overseas*/ '/hk/v3/statements'])
+                || /*the complaint image*/ (0 === strncasecmp($url, '/v3/merchant-service/images/', 28) && false === stripos($url, 'upload', 28))
+            ) {
+                // Notes: For overseas developers, the response's validation was different with the mainland's architecture,
+                //        and it was related with both of the stream's `SHA1` and the `Wechatpay-Statement-Sha1` header value.
+                //        Skip and postpone here for contributing.
+                return $response;
+            }
+
             if (!($response->hasHeader(WechatpayNonce) && $response->hasHeader(WechatpaySerial)
                 && $response->hasHeader(WechatpaySignature) && $response->hasHeader(WechatpayTimestamp))) {
最后一次编辑于  2022-06-24  
点赞 5
收藏
评论

3 个评论

  • 青寒
    青寒
    2022-05-20

    无需思考,直接点赞!

    2022-05-20
    赞同 2
    回复
  • 北望沣渭
    北望沣渭
    2022-05-24

    1.4.5 - 2022-05-21

    • 新增APIv3请求/响应特殊验签逻辑,国内两个下载接口自动忽略验签,海外商户账单下载仅验RSA签名,详见 #94
    • 新增APIv3海外商户账单下载测试用例,示例说明如何验证流SHA1摘要;


    2022-05-24
    赞同
    回复
  • 拾忆
    拾忆
    发表于移动端
    2022-05-20
    node版本同步了吗?
    2022-05-20
    赞同
    回复 3
    • 北望沣渭
      北望沣渭
      2022-05-20
      还没有。。。我以为没人有此需求。。。
      2022-05-20
      回复
    • 拾忆
      拾忆
      2022-05-20回复北望沣渭
      哈哈,我只是问问
      2022-05-20
      回复
    • 北望沣渭
      北望沣渭
      2022-05-20回复拾忆
      回头补上
      2022-05-20
      回复
登录 后发表内容