Java中的微信支付(3):API V3微信支付签名验证

1. 前言

微信支付V3版本前两篇分别讲了如何对请求做签名和如何获取并刷新微信平台公钥,本篇将继续展开如何对微信支付响应结果的验签。

2. 为什么要对响应验签

微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须验证响应的签名,保证响应确实来自微信支付服务器,避免中间人攻击。而验证响应签名除了需要微信平台的公钥外还需要从请求头的其它参数。

假设以下就是微信支付服务器的响应:

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 02 Apr 2019 12:59:40 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2204
Connection: keep-alive
Keep-Alive: timeout=8
Content-Language: zh-CN
Request-ID: e2762b10-b6b9-5108-a42c-16fe2422fc8a
Wechatpay-Nonce: c5ac7061fccab6bf3e254dcf98995b8c
Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==
Wechatpay-Timestamp: 1554209980
Wechatpay-Serial: 5157F09EFDC096DE15EBE81A47057A7232F1B8E1
Cache-Control: no-cache, must-revalidate

{"prepay_id":"wx2922034726858082fbd40b511c67630000"}

检查平台证书序列号

微信支付响应的时候会携带一个微信平台证书序列号,从响应头中的Wechatpay-Serial字段中获取值,用来提示我们要使用该序列号的证书来进行验签,如果不存在就需要我们刷新证书,而上一文我们将平台证书序列号和证书以键值对存在HashMap中,我们只需要检查是否存在即可,不存在就刷新。

构造验签名串

从响应结果中获取对应下面方法的三个参数就可以构造出验签名串。

/**
 * 构造验签名串.
 *
 * @param wechatpayTimestamp HTTP头 Wechatpay-Timestamp 中的应答时间戳。
 * @param wechatpayNonce     HTTP头 Wechatpay-Nonce 中的应答随机串
 * @param body               响应体
 * @return the string
 */
public String responseSign(String wechatpayTimestamp, String wechatpayNonce, String body) {
    return Stream.of(wechatpayTimestamp, wechatpayNonce, body)
            .collect(Collectors.joining("\n", "", "\n"));
}

验证签名

待验证的签名从响应头中的Wechatpay-Signature字段中获取,我们使用微信支付平台公钥对验签名串和签名进行SHA256 with RSA签名验证。

// 构造验签名串  
        final String signatureStr = responseSign(wechatpayTimestamp, wechatpayNonce, body);
   // 加载SHA256withRSA签名器
        Signature signer = Signature.getInstance("SHA256withRSA");
  // 用微信平台公钥对签名器进行初始化
        signer.initVerify(certificate);
   // 把我们构造的验签名串更新到签名器中
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
   // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证
        boolean result = signer.verify(Base64Utils.decodeFromString(wechatpaySignature));

 完整的验签代码

/**
 * 我方对响应验签,和应答签名做比较,使用微信平台证书.
 *
 * @param wechatpaySerial    response.headers['Wechatpay-Serial']    当前使用的微信平台证书序列号
 * @param wechatpaySignature response.headers['Wechatpay-Signature']   微信平台签名
 * @param wechatpayTimestamp response.headers['Wechatpay-Timestamp']  微信服务器的时间戳
 * @param wechatpayNonce     response.headers['Wechatpay-Nonce']   微信服务器提供的随机串
 * @param body               response.body 微信服务器的响应体
 * @return the boolean
 */
@SneakyThrows
public boolean responseSignVerify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) {

    if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
        refreshCertificate();
    }
    Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);

    final String signatureStr = createSign(wechatpayTimestamp, wechatpayNonce, body);
    Signature signer = Signature.getInstance("SHA256withRSA");
    signer.initVerify(certificate);
    signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));

    return signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
}

CERTIFICATE_MAP 平台证书容器可参考上一篇文章。

3. 总结

验签通过就说明我们请求的响应来自微信服务器就可以针对结果进行对应的逻辑处理了,微信支付API无论是V2还是V3都包含了使用Api证书对请求进行加签,对响应结果进行验签的流程,十分考验对密码摘要算法的使用,其它就是组织参数调用Http请求。如果你能够掌握这一能力就会在面试中和工作中占到优势。好了今天分享就到这里,多多关注: 码农小胖哥 获取更多实用的编程干货。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是Java微信支付V3验证签名代码示例: ```java public class WechatPayVerification { /** * 验证签名 * * @param body 请求报文主体 * @param signature 微信支付返回的签名 * @param timestamp 微信支付返回的时间戳 * @param nonce 微信支付返回的随机字符串 * @param publicKey 商户平台API证书公钥内容 * @return 验证成功返回true,否则返回false */ public static boolean verify(String body, String signature, String timestamp, String nonce, String publicKey) { try { // 拼接待签名字符串 String message = timestamp + "\n" + nonce + "\n" + body + "\n"; // 将公钥字符串转换为公钥对象 PublicKey publicKeyObj = getPublicKey(publicKey); // 验证签名 Signature sign = Signature.getInstance("SHA256withRSA"); sign.initVerify(publicKeyObj); sign.update(message.getBytes()); return sign.verify(Base64.getDecoder().decode(signature)); } catch (Exception e) { e.printStackTrace(); } return false; } /** * 将公钥字符串转换为公钥对象 * * @param publicKeyStr 商户平台API证书公钥内容 * @return 公钥对象 * @throws Exception 异常 */ private static PublicKey getPublicKey(String publicKeyStr) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } } ``` 使用示例: ```java String body = "{\"id\":\"123456\"}"; String signature = "Y5iHYv5RyajCQ2e0gLdK0l1vFqZpDq0NqOuLWmzv7y4t4kzO7EhO5Qjv7xuS9OcX7s1sB2QkIwPzDjKsTGMeTg=="; String timestamp = "1607993739"; String nonce = "nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6"; String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjvJwOwFf7GmzQY+KcSEZrLJrL9rDdM0LpO+u4w1lu4B4f8Jg+4zE5xjZS+7XuJ2f/CQ7h+Q2D5R7R5Y5Y5o5v3qZ3Hc9X8LW1n0LJw5DQMYca12R8GtWk5v5H3lqfZ9XN8gW0hJFyO4cq4QE4b8CkWpLdOxuKqb07SUI7kcD2h06Ov7k6zAAbAzP57F6Y2sI1cXx1ZrT9rKlPnPOgI7w3HnF6kzgD1YjBvzQ6U2J6U9+6bGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6nYut1vzqJHKBn3sxq8UGaGv6vzR6U9Q6"; boolean result = WechatPayVerification.verify(body, signature, timestamp, nonce, publicKey); System.out.println(result); ``` 注意:在实际使用,需要将公钥字符串替换为商户平台API证书的公钥内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值