一、验证签名时要认真阅读文档!!
1.取出请求头备用
2.先拿到微信平台证书
注意事项
3. 拿到其他的请求头
注意事项
这里有个大坑,不知道其他人是否遇到,获取应答主体时,我转JSON时顺序乱掉了;
这里顺序千万不能乱! 这里顺序千万不能乱! 这里顺序千万不能乱!
起初我按照 回调支付结果文档 中去设置顺序发现文档中排列的顺序与我接收的顺序不一样,效果如下:
微信文档中的:
我接收到的:
所以要以一定要以应答主体为准!!!
java中最好使用 request.getInputStream() 获取
4.开始验签
- 对验签名串信息进行整理
应答时间戳\n
应答随机串\n
应答报文主体\n
- 获取应答签名 Wechatpay-Signature 并进行Base64解密
- 使用刚刚获取的微信支付平台的证书对验签名串和签名进行SHA256 with RSA签名验证
二、附上部分代码
/**
* 得到微信应答签名
*
* @return
*/
public boolean getWeChatPayReplySign(String serialNo, String weChatPayTimestamp, String weChatPayNonce, String body, String weChatPaySignature) {
String str = Stream.of(weChatPayTimestamp, weChatPayNonce, body).collect(Collectors.joining("\n", "", "\n"));
System.out.println(str);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(this.getCertificate(serialNo));
sign.update(str.getBytes(StandardCharsets.UTF_8));
// 解码后的字符串
return sign.verify(Base64.decodeBase64(weChatPaySignature));
}
/**
* 获取token
*
* @param method
* @param url
* @param nonceStr
* @param body
* @return
*/
public String getToken(String method, String url, String nonceStr, String body) {
long timestamp = System.currentTimeMillis() / 1000;
String message = Stream.of(method, url, String.valueOf(timestamp), nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
String signature = getWeChatSign(message);
return new StringBuilder("mchid=\"").append(wxConfig.getMchId()).append("\",")
.append("nonce_str=\"").append(nonceStr).append("\",")
.append("timestamp=\"").append(timestamp).append("\",")
.append("serial_no=\"").append(wxConfig.getMchSerialNo()).append("\",")
.append("signature=\"").append(signature).append("\"").toString();
}
/**
* 解密请求体
*
* @param dto
* @return
*/
public String decryptResponseBody(EncryptCertificateDTO dto) {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec("ApiV3Key".getBytes("utf-8"), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, dto.getNonce().getBytes());
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(dto.getAssociated_data().getBytes());
return new String(cipher.doFinal(Base64.decodeBase64(dto.getCiphertext())), "utf-8");
}
/**
* 得到证书
*
* @param serialNo
* @return
*/
private Certificate getCertificate(String serialNo) {
if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(serialNo)) {
createCertificates(serialNo);
}
return CERTIFICATE_MAP.get(serialNo);
}
/**
* 创建证书
*
* @param serialNo
*/
private void createCertificates(String serialNo) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
List<CertificateDTO> certificates = getCertificates();
if (CollectionUtils.isEmpty(certificates)) {
throw new IllegalArgumentException("证书获取失败");
}
CertificateDTO certificateDTO = certificates.stream().filter(item -> serialNo.equals(item.getSerial_no())).findFirst().orElseThrow(() -> new IllegalArgumentException("证书获取失败"));
Date now = new Date();
if (format.parse(certificateDTO.getEffective_time()).before(now) && format.parse(certificateDTO.getExpire_time()).after(new Date())) {
// 解密数据
String publicKey = decryptResponseBody(certificateDTO.getEncrypt_certificate());
final CertificateFactory cf = CertificateFactory.getInstance("X509");
ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
Certificate certificate = cf.generateCertificate(inputStream);
// 清理HashMap
CERTIFICATE_MAP.clear();
// 放入证书
CERTIFICATE_MAP.put(serialNo, certificate);
}
}
/**
* 获取微信平台证书
*
* @return
*/
private List<CertificateDTO> getCertificates() {
HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");
httpGet.setHeader("Accept", "application/json");
httpGet.setHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
String authorization = getToken("GET", "/v3/certificates", IdUtil.fastSimpleUUID(), "");
httpGet.setHeader("Authorization", WE_CHAT_PAY2 + authorization);
HttpClient httpClient = HttpClients.custom().build();
// 执行
HttpResponse response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() == 200) {
JSONObject entity = JSONUtil.parseObj(EntityUtils.toString(response.getEntity()));
return JSONUtil.toList(entity.getStr("data"), CertificateDTO.class);
}
return null;
}
总结
文档一定要仔细看好,否则太耽误开发时间了
最后文章有什么不正确的地方请及时联系我改正,谢谢!