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

大家好,我是小悟

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"}

则验证签名串为

1584958205
932855908e9746623b5e958b2c4d5300
{"prepay_id":"up_wx2318713100628571528995372959389200"}

注意,对获取到的Wechat-Signature的字段值使用Base64进行解码,得到应答 签名。

image.png

3、验证签名

public static boolean v3VerifyRSA(String data,byte[] sign, String wechatPubKeyPath) throws Exception{
  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;
  }

最后整理的验签工具类

public class SignUtils {
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

您的一键三连,是我更新的最大动力,谢谢

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容