wechatpay-apiv3 / wechatpay-guzzle-middleware

微信支付 APIv3 Guzzle HTTP Client中间件(middleware)
Apache License 2.0
208 stars 46 forks source link

WechatPayMiddleware.php 在 调用下载账单API时,有bug #46

Closed titop closed 3 years ago

titop commented 3 years ago

WechatPayMiddleware.php文件,第107~124行

            return $handler($request, $options)->then(
                function (ResponseInterface $response) use ($request) {
                    $code = $response->getStatusCode();
                    if ($code >= 200 && $code < 300) {
                        if (!$response->getBody()->isSeekable() && \class_exists("\\GuzzleHttp\\Psr7\\CachingStream")) {
                            $response = $response->withBody(new \GuzzleHttp\Psr7\CachingStream($response->getBody()));
                        }
                        if (!$this->validator->validate($response)) {
                            if (\class_exists('\\GuzzleHttp\\Exception\\ServerException')) {
                                throw new \GuzzleHttp\Exception\ServerException(
                                    "应答的微信支付签名验证失败", $request, $response);
                            } else {
                                throw new \RuntimeException("应答的微信支付签名验证失败", $code);
                            }
                        }
                    }
                    return $response;
                }
            );

接口返回的是json格式时,第114行会对返回数据进行合法性检验。

但是在 调用下载账单API 使用https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx 请求,返回的不是json,而是文本字符串,第114行没有对这种情况进行判断,导致抛出 "应答的微信支付签名验证失败"异常。

改成这样,可能适应性会更好一些。

                        if (strtolower($request->getHeader('Accept')[0]) === 'application/json' && !$this->validator->validate($response)) {
                            if (\class_exists('\\GuzzleHttp\\Exception\\ServerException')) {
                                throw new \GuzzleHttp\Exception\ServerException(
                                    "应答的微信支付签名验证失败", $request, $response);
                            } else {
                                throw new \RuntimeException("应答的微信支付签名验证失败", $code);
                            }
                        }

如果下载,返回的不是json格式字符串,在header中不传

[ 
    'Accept' => 'application/json' 
]

或将 Accept设置为/,接口就能正常返回数据。

xy-peng commented 3 years ago

欢迎pr

C-GM commented 3 years ago

WechatPayMiddleware.php文件,第107~124行

            return $handler($request, $options)->then(
                function (ResponseInterface $response) use ($request) {
                    $code = $response->getStatusCode();
                    if ($code >= 200 && $code < 300) {
                        if (!$response->getBody()->isSeekable() && \class_exists("\\GuzzleHttp\\Psr7\\CachingStream")) {
                            $response = $response->withBody(new \GuzzleHttp\Psr7\CachingStream($response->getBody()));
                        }
                        if (!$this->validator->validate($response)) {
                            if (\class_exists('\\GuzzleHttp\\Exception\\ServerException')) {
                                throw new \GuzzleHttp\Exception\ServerException(
                                    "应答的微信支付签名验证失败", $request, $response);
                            } else {
                                throw new \RuntimeException("应答的微信支付签名验证失败", $code);
                            }
                        }
                    }
                    return $response;
                }
            );

接口返回的是json格式时,第114行会对返回数据进行合法性检验。

但是在 调用下载账单API 使用https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx 请求,返回的不是json,而是文本字符串,第114行没有对这种情况进行判断,导致抛出 "应答的微信支付签名验证失败"异常。

改成这样,可能适应性会更好一些。

                        if (strtolower($request->getHeader('Accept')[0]) === 'application/json' && !$this->validator->validate($response)) {
                            if (\class_exists('\\GuzzleHttp\\Exception\\ServerException')) {
                                throw new \GuzzleHttp\Exception\ServerException(
                                    "应答的微信支付签名验证失败", $request, $response);
                            } else {
                                throw new \RuntimeException("应答的微信支付签名验证失败", $code);
                            }
                        }

如果下载,返回的不是json格式字符串,在header中不传

[ 
    'Accept' => 'application/json' 
]

或将 Accept设置为/,接口就能正常返回数据。

这个bug怎么没有反馈呢,我也遇到了,还没解决呢……

xy-peng commented 3 years ago

建议先使用空的validator来发请求

use WechatPay\GuzzleMiddleware\Validator;

class NoopValidator implements Validator
{
    public function validate(\Psr\Http\Message\ResponseInterface $response)
    {
        return true;
    }
}

$wechatpayMiddleware = WechatPayMiddleware::builder()
    ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey)
    ->withValidator(new NoopValidator) // NOTE: 设置一个空的应答签名验证器,**不要**用在业务请求
    ->build();
TheNorthMemory commented 3 years ago

建议这块处理逻辑这么弄:

  1. 依赖上文返回的 hash_typehash_value 构建一个Validator,对返回内容验签
  2. download_url 上,query 参数可能存有 tartype 字典,验签逻辑要兼顾
  3. download_url 只有30秒有效时间,可选验签逻辑加入有效性校验(这可能需要官方body JSON增加返回字段明示)
xy-peng commented 3 years ago

这个bug怎么没有反馈呢,我也遇到了,还没解决呢……

补充说明下,这一点并不是bug而不修复。应答签名是保证应答报文的真实性、完整性和抗抵赖的关键信息,我们不希望增加类似判断content-type不等于application/json就不验签的“后门”。

我们的建议是调用方主动在账单文件下载接口使用NoopValidator,只对这一个接口不验签。未来可能会在sdk中去提供具体的api供开发者直接使用。

@TheNorthMemory 的建议更好,该接口适用的Validator应使用上一步获取的账单摘要对获得的账单进行完整性校验。

C-GM commented 3 years ago

建议这块处理逻辑这么弄:

  1. 依赖上文返回的 hash_typehash_value 构建一个Validator,对返回内容验签
  2. download_url 上,query 参数可能存有 tartype 字典,验签逻辑要兼顾
  3. download_url 只有30秒有效时间,可选验签逻辑加入有效性校验(这可能需要官方body JSON增加返回字段明示)

在微信的开发平台上已经获取了您的解决办法,再次感谢!