电商收付通系列③,对微信应答或回调进行签名验证

简介: 如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名。建议商户验证应答签名。同样的,微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须验证回调的签名,以确保回调是由微信支付发送。这里我们就要用到在电商收付通系列②,获取微信支付平台证书获取的微信支付平台证书中的公钥。再次提醒,应答和回调的签名验证使用的是微信支付平台证书,不是商户API证书。使用商户API证书是验证不过的。

1、签名验证

如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名。建议商户验证应答签名。同样的,微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须验证回调的签名,以确保回调是由微信支付发送。这里我们就要用到在电商收付通系列②,获取微信支付平台证书获取的微信支付平台证书中的公钥。再次提醒,应答和回调的签名验证使用的是微信支付平台证书,不是商户API证书。使用商户API证书是验证不过的。

image.png

2、构造验证签名串

首先,商户先从应答中获取以下信息。
HTTP头Wechatpay-Timestamp中的应答时间戳。

HTTP头Wechatpay-Nonce中的应答随机串

应答主体(response Body)

然后,请按照以下规则构造应答的验签名串。签名串共有三行,行尾以\n结束,包

括最后一行。\n为换行符(ASCII编码值为0x0A)。若应答报文主体为空

(如HTTP状态码为204 No Content),最后一行仅为一个\n换行符。

String headsTimestamp = headers.get("Wechatpay-Timestamp").get(0);
String headsNonce = headers.get("Wechatpay-Nonce").get(0);
String headsSign = headers.get("Wechatpay-Signature").get(0);
//应答报文主体,如果没有则是空串""
String resContent = response.body();

//拼装待签名串
StringBuilder sb =new StringBuilder();
sb.append(headsTimestamp).append("\n");
sb.append(headsNonce).append("\n");
sb.append(resContent).append("\n");

如某个应答的HTTP报文为

Response Headers: 
    Keep-Alive=[timeout=8]
    null=[HTTP/1.1 200 OK]
    Wechatpay-Timestamp=[1584958205]
    Server=[nginx]
    X-Content-Type-Options=[nosniff]
    Connection=[keep-alive]
    Date=[Mon, 23 Mar 2020 10:10:05 GMT]
    Wechatpay-Serial=[6E1D414B17AFC0A15A2E4491C9C14927801AF3EC]
    Wechatpay-Nonce=[932855908e9746623b5e958b2c4d5300]
    Wechatpay-Signature=[NY6EdtV89SH9FVHEV4XepdF4cVA2RtJndL+AnXlhmW1O+DA4mRBJ27nUiFiUxyK3wkHP2rFR00Ic8YZ+iNmyclipcTOK3f+afJ06zJj9x2FzDftvYqoB/KzaxcZ3YD9MqAsMM1c7kK/jvmAI3GbuutVbS+r1wrh8AylLXVQPNr7Yrm3qASZV5q9P/+BM+BvubN2Bh8VP4TQ9rprYxufnlMsHXgniHg3Y6V9ClLAHfiTh25IKDpdLXPf2Bsjsrq17tc5ZsAyc7HnorFJU1qHbHBB/wTWwyjnmLx2eRLpU/j6cMhDf1Pl7+mbNH3zCZPus/TS9dT6R+NJTNgf23iHiiQ==]
    Cache-Control=[no-cache, must-revalidate]
    Content-Length=[55]
    Content-Language=[zh-CN]
    Request-ID=[7nik1t]
    Content-Type=[application/json; charset=utf-8]
Response Body: 
    {"prepay_id":"up_wx2318713100628571528995372959389200"}

则验证签名串为

932855908e9746623b5e958b2c4d5300
{"prepay_id":"up_wx2318713100628571528995372959389200"}

签名

image.png

3、验证签名

  if(data == null || sign == null || wechatPubKeyPath == null){
      return false;
  }
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  FileInputStream in =new FileInputStream(wechatPubKeyPath);
  Certificate c = cf.generateCertificate(in);
  in.close();
  PublicKey publicKey = c.getPublicKey();
  Signature signature = Signature.getInstance("SHA256WithRSA");
  signature.initVerify(publicKey);
  signature.update(data.getBytes(StandardCharsets.UTF_8));

  boolean result = signature.verify(sign);
  if (result) {
         logger.info("v3VerifyRSA result:{}","签名验证成功");
      } else {
         logger.info("v3VerifyRSA result:{}","签名验证失败");
      }
        return result;
  }

最后整理的验签工具类

private static final Logger logger = LoggerFactory.getLogger(SignUtils.class);
/**
* 签名验证
* @param response
* @param wechatPubKeyPath
* @return
*/
public static boolean v3VerifyRSA(HttpResponse response,String wechatPubKeyPath) {

  if (response == null || StringUtils.isEmpty(wechatPubKeyPath)) {
      return false;
  }
  Map<String, List<String>> headers = response.headers();
  //验证微信支付返回签名
  String headsTimestamp = headers.get("Wechatpay-Timestamp").get(0);
  String headsNonce = headers.get("Wechatpay-Nonce").get(0);
  String headsSign = headers.get("Wechatpay-Signature").get(0);
  String resContent = response.body();
  //拼装待签名串
  StringBuilder sb =new StringBuilder();
  sb.append(headsTimestamp).append("\n");
  sb.append(headsNonce).append("\n");
  sb.append(resContent).append("\n");
  try {
      //验证签名
      return v3VerifyRSA(sb.toString(), Base64.decodeBase64(headsSign.getBytes()), wechatPubKeyPath);
  } catch (Exception e) {
      e.printStackTrace();
      return false;
  }
}
}

image.png

4、结果

image.png

image.png

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

上一篇:电商收付通系列②,获取微信支付平台证书

相关文章
|
7月前
|
弹性计算 前端开发 小程序
微信小程序上传文件至阿里云OSS直传(java后端签名+前端直传)
当前的通用文件上传方式是通过前端上传到服务器,再由服务器转存至对象存储。这种方式在处理小文件时效率尚可,但大文件上传因受限于服务器带宽,速度较慢。例如,一个100MB的文件在5Mbps带宽的阿里云ECS上上传至服务器需160秒。为解决此问题,可以采用后端签名的方式,使微信小程序直接上传文件到阿里云OSS,绕过服务器中转。具体操作包括在JAVA后端引入相关依赖,生成签名,并在微信小程序前端使用这个签名进行文件上传,注意设置正确的请求头和formData参数。这样能提高大文件上传的速度。
1238 1
|
7月前
|
前端开发 开发者
【微信公众号对接】有关签名一直报错,提示invalid signature问题(我的签名和使用微信开发者工具验证返回的签名的是一致的)但还是报错!!!
【微信公众号对接】有关签名一直报错,提示invalid signature问题(我的签名和使用微信开发者工具验证返回的签名的是一致的)但还是报错!!!
502 0
|
4月前
|
小程序
手写签名-微信小程序
手写签名-微信小程序
74 1
|
JavaScript 算法 Java
企业微信开启接收消息+验证URL有效性
企业微信开启接收消息、验证URL有效性、SHA1、提供接收和推送给企业微信消息的加解密接口、计算消息签名接口
256 1
企业微信开启接收消息+验证URL有效性
|
5月前
|
移动开发 JavaScript
thinkPHP5.0开发微信H5页面分享接口signature验证失败,signature与微信 JS 接口签名校验工具返回结果不一致
thinkPHP5.0开发微信H5页面分享接口signature验证失败,signature与微信 JS 接口签名校验工具返回结果不一致
96 0
|
5月前
|
PHP 开发工具
tp5+微信公众号服务器配置时使用官方sdk还是token验证失败
tp5+微信公众号服务器配置时使用官方sdk还是token验证失败
47 0
|
7月前
|
安全 Java Linux
企业微信应用结合Cpolar内网穿透实现固定域名验证回调本地接口服务
企业微信应用结合Cpolar内网穿透实现固定域名验证回调本地接口服务
|
7月前
|
前端开发 开发者
【微信公众号对接】有关签名一直报错,提示invalid signature问题(我的签名和使用微信开发者工具验证返回的签名的是一致的)但还是报错!!!
【微信公众号对接】有关签名一直报错,提示invalid signature问题(我的签名和使用微信开发者工具验证返回的签名的是一致的)但还是报错!!!
97 0
|
7月前
|
JSON JavaScript 前端开发
全面的.NET微信网页开发之JS-SDK使用步骤、配置信息和接口请求签名生成详解
全面的.NET微信网页开发之JS-SDK使用步骤、配置信息和接口请求签名生成详解
如何验证企业微信生成的token是否有效?
如何验证企业微信生成的token是否有效?
127 0