回调验签失败
AFatMan opened this issue · 12 comments
异常信息
com.wechat.pay.contrib.apache.httpclient.exception.ValidationException: 验签失败:serial=[4FE088A8E7AC57DBA7A29F37BAA76ADA8F9EAD05] message=[1655705237 81xkrdXEvgG5vM2ROhHJybbEwq84n3MG {"summary":"支付成功","resource":{"ciphertext":"B7tyg5HKxbeTURxDdPs3i50gyDBpYQvQUHi9v1dyrXWIyzifKcSv9Wmc76DUsqUs4PSdplOxtdf83dG7+By2wt1Odmj9VmG9KmFQi9Lwxl+kH4MV87mm55lJzhplN0zpCoEQzyMWZ+b6pNCQCc8/lxE7H40Y8n+LUIPdFFf2zcPA789PckDcE1XrUudu6JnhbAw3UywCv/P/Z6r056HH4Xpjq1ny+F9K4FMBwHPiphd706EHK5NUJz3FKwYQWj+V+nqKuWDsPRbLFZTUliFsjTJ9yxAjZbEP1zIfTLlfQ3kLgQtblr6VTu/hZCn5LKecsHvyKvCuDN2l31ohiBf/uZqDoesy4NxYp2mWz5jmsqxxIMkkl+7fJYVdLdIyOJPll6QS9NVAXZvjAC5BlmkaWf3cnhfDqWHsxvba9+pOeBJ5jJohOC40cLAPtmGEi8H+kij6B2ie3mhHKibd3ctxy36y2yAH/cmSYbI1YH1IspoNiQofu5mUf1VcFGfYWTmi9ruE/N5QOyctK354nquhBbEBwwj47DKvq2Gu4JHMt/QPFRReZ3bb7nch+NdqNtdIgPorY7123mzTpEIHNDQe","originalType":"transaction","associatedData":"transaction","nonce":"JiRoi3NoMIPo","algorithm":"AEAD_AES_256_GCM"},"eventType":"TRANSACTION.SUCCESS","createTime":"2022-06-20T14:07:17+08:00","id":"a61e735c-0006-562c-afa4-ebb2819e8124","resourceType":"encrypt-resource"} ] sign=[Hsdtq936PC1zZ6CI2cunCO0ruqcgg1ox7ujwSpbkaPIqPoQN0m2kovae8K2Pp7rhgpid70XUR+nuilslOuNBZmPrugSJJwoqe/wuHv3oZ/YUYjv7SBCKoFpm5qFVPB5/HXOvOh9QWpkupram+2uv/1Z4M/v+nwXKBbK7QwKQL7lFjUJ9qMHKVlbaUpyZpifJrr1sdLXuLMc7tcfv36x6ofuio5Kfwl8qr97da0BIGjj238P4JvgBTH/U3Ff1hRYlHX80xTsQgpxFQ3eglEqGNeqkQtU32YAgfDTGBeoIOGi2D7gpdksgUGd2WZ0bE0pBIiH6dyw4C29mbtdYCNsV8w==]
可以先对比一下传入NotificationHandler的信息serialNumber、timestamp、nonce、body是否跟原始的HTTP请求参数完全一致,不一致会导致验签失败。
可以先对比一下传入NotificationHandler的信息serialNumber、timestamp、nonce、body是否跟原始的HTTP请求参数完全一致,不一致会导致验签失败。
从请求参数上获取的,是一致的
方便的话麻烦贴一下设置参数的代码,以及检查下body参数有没有编码问题,例如=号转成\u003d的问题。
public String nativeNotify(HttpServletRequest request, HttpServletResponse response) throws ValidationException, ParseException {
//获取body
StringBuilder sb = new StringBuilder();
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("读取数据流异常:{}", e.toString());
}
log.info("body:" + sb);
String body = sb.toString();
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");
//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
log.info("通知回调请求参数: body: {} ,nonceStr: {} ,signature: {} ,serialNo: {} ,timestamp: {}", body, nonceStr,
signature,
serialNo,
timestamp);
// 签名的验证及解密
// 构建request,传入必要参数
NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serialNo)
.withNonce(nonceStr)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(wxPayVerifier, "apiv3密钥".getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
Notification notification = handler.parse(notificationRequest);
// 从notification中获取解密报文
System.out.println(notification.getDecryptData());
log.info("支付通知的解密数据: {}", notification.getDecryptData());
log.info("通知验签成功");
response.setStatus(200);
return new JSONObject() {{
set("code", "SUCCESS");
set("message", "成功");
}}.toString();
}
/*
配置类
*/
@Bean
@SneakyThrows
public Verifier wxPayVerifier(WxPayProperties wxPayProperties) {
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 私钥签名对象(签名)
PrivateKeySigner privateKeySigner = new PrivateKeySigner("商户证书序列号", wxPayPrivateKey(wxPayProperties));
// 身份认证对象(验签)
WechatPay2Credentials credentials = new WechatPay2Credentials("商户号",
privateKeySigner);
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant("商户号", credentials, "apiv3密钥".getBytes(StandardCharsets.UTF_8));
// 使用定时更新的签名验证器,不需要传入证书
return certificatesManager.getVerifier("商户号");
}
// 私钥
@Bean
public PrivateKey wxPayPrivateKey(WxPayProperties wxPayProperties) {
try {
InputStream inputStream = new ClassPathResource("私钥文件.pem路径").getInputStream();
return PemUtil.loadPrivateKey(inputStream);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("私钥文件不存在", e);
}
}
还需要额外补充其他代码么?
我测试了你提供的这个id为a61e735c-0006-562c-afa4-ebb2819e8124的回调通知,使用原始的网络请求参数是可以通过的。
看代码header的获取应该没问题,建议调试对比下网络请求的header参数和body参数和真正获取的是否一致。
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
@AFatMan 不要用 BufferedReader
,readline()读入时,返回的 line 中没有包含行尾的换行,导致 body 和原始数据不一致了。
类似于下面这样子。
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int length; (length = inputStream.read(buffer)) != -1; ) {
result.write(buffer, 0, length);
}
更多更详细,可以参考How do I read / convert an InputStream into a String in Java?。
首先,感谢大佬这么晚仍在为我解答疑惑 @xy-peng
然后使用您推荐的种方法,并且我把我代码里之前某些地方用的new替换为spring管理的bean后,验签成功了
感谢!!!
哎,我也遇到一样的问题了。。。换行符有关的都尝试了一遍,还是不行
public Transaction validSign(HttpServletRequest request) throws ValidationException{
String wechatPaySerial = request.getHeader("Wechatpay-Serial");
String wechatpayNonce = request.getHeader("Wechatpay-Nonce");
String wechatSignature = request.getHeader("Wechatpay-Timestamp");
String wechatTimestamp = request.getHeader("Wechatpay-Signature");
String requestBody = getRequestBody(request);
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPaySerial)
.nonce(wechatpayNonce)
.signature(wechatSignature)
.timestamp(wechatTimestamp)
.body(requestBody)
.build();
// 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
// 没有的话,则构造一个
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
return parser.parse(requestParam, Transaction.class);
}
private String getRequestBody(HttpServletRequest request) {
String body;
try (ServletInputStream inputStream = request.getInputStream()
) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int length; (length = inputStream.read(buffer)) != -1; ) {
result.write(buffer, 0, length);
}
body = result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
return body;
}
2023-10-11T23:14:53.101+08:00 INFO 248827 --- [nio-8081-exec-2] c.example.demo.controller.BuyController : notice pay failed: sign verification failed! Processing WechatPay notification,signature verification failed,signType[WECHATPAY2-SHA256-RSA2048] serial[77A42067DABBAF4B76EA6575AFD598CF712D604B] message[lXPX2+KDHxZY23S02fBACOCHvhr+Mbu6JkcA/j3cjWNYSOYH8cH+FlaeKExLWxOS4WPf+PshtED3yaHn/mYMBcQ7CAYYtGe7zWQVIQXELqrTL28aIkOqKxFagRbFVgzhmjJLCCIzWQNdasl6CTsEnCwFGA9W76OCeBK10N5Hl+iZqUP81qBlx09J+WBpRtAsNIJorbqXCj+ZuIDjvqlfF16woiJG+9wgbJBQ2tV3/+FDA232XBj381dV8/npnY2ZeD0Rdfj0M/RGMjIiRbAfbctjHe7jQYVKuRxn8iejiI6DBRZRgFm3NhW9VyT3CNsc3gJRcpKJdhVeykc/odumyQ==
BW99mCrQI0FFWb5GKOhEwduVhoXmAPoL
{"id":"7f8fcdb2-573a-547a-901e-4c34f9761293","create_time":"2023-10-11T22:40:46+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"8TKBc3NQAPapdskLACAFRX2W+g5TyrEihNRIVZdJau79OJjYU/HQjRzzx8uRKM/iDuwLVWfyW028KpHcEqtNvk27v4SAwKVlMQeJJdcqa56SVdSgTzEfWE6TQsY/EzH2/z0pDuLhQ90lej3WMHpQSPMuL4tC2rdxcH5347UZlGdjzaCAg45a0RIH/RWup7hyRBG2glXZJH9Oz5yaGVSvhkXJtcz1YyTVJKyU1kPUfKuXMX/QcNQ4wTs6Zy3mcui/D4Jwu0Fv7iJ48D2EIo+S7lgilsJGl9mBtKDQdmKWMcayfv+Sd2RUN/x5Wd2B7D1rCSiP+sneG9IZ2qss6YzgekCac3ceHZ3YJeHJYACvLGdh4RciMb3/SEXxsTwQ4SeenHSmxAPCkFbf31CBOKJpDcSgeNSMKUQ0fbuue3+ccgpt65Cs4SMUiHZEGmeMnYEIozysMEhWsecbusE8UEHrEsigT4h2QtK2GEtKVtXy05tnk3cPkgFHt6PKsjMi9ii+RZ3AnBGzX12BfKlEj6xzQDWO8DYaBHU4UHCeKi1yy/I1Van5Rogn","associated_data":"transaction","nonce":"4qkJp4DV2urE"}}
] sign[1697037292]