1、签名验证
如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名。建议商户验证应答签名。同样的,微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须验证回调的签名,以确保回调是由微信支付发送。这里我们就要用到在电商收付通系列②,获取微信支付平台证书获取的微信支付平台证书中的公钥。再次提醒,应答和回调的签名验证使用的是微信支付平台证书,不是商户API证书。使用商户API证书是验证不过的。
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"}
签名
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;
}
}
}
4、结果
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海