Java中的微信支付(3):API V3對微信服務(wù)器響應(yīng)進(jìn)行簽名驗證
1. 前言
微信支付 V3 版本前兩篇分別講了如何對請求做簽名和如何獲取并刷新微信平臺公鑰,本篇將繼續(xù)展開如何對微信支付響應(yīng)結(jié)果的驗簽。
2. 為什么要對響應(yīng)驗簽
微信支付會在回調(diào)的 HTTP 頭部中包括回調(diào)報文的簽名。商戶必須驗證響應(yīng)的簽名,保證響應(yīng)確實來自微信支付服務(wù)器,避免中間人攻擊。而驗證響應(yīng)簽名除了需要微信平臺的公鑰外還需要從請求頭的其它參數(shù)。
假設(shè)以下就是微信支付服務(wù)器的響應(yīng):
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"}
檢查平臺證書序列號
微信支付響應(yīng)的時候會攜帶一個微信平臺證書序列號,從響應(yīng)頭中的Wechatpay-Serial字段中獲取值,用來提示我們要使用該序列號的證書來進(jìn)行驗簽,如果不存在就需要我們刷新證書,而上一文我們將平臺證書序列號和證書以鍵值對存在HashMap中,我們只需要檢查是否存在即可,不存在就刷新。
構(gòu)造驗簽名串
從響應(yīng)結(jié)果中獲取對應(yīng)下面方法的三個參數(shù)就可以構(gòu)造出驗簽名串。
/**
?*?構(gòu)造驗簽名串.
?*
?*?@param?wechatpayTimestamp HTTP頭 Wechatpay-Timestamp 中的應(yīng)答時間戳。
?*?@param?wechatpayNonce?????HTTP頭?Wechatpay-Nonce?中的應(yīng)答隨機(jī)串
?*?@param?body???????????????響應(yīng)體
?*?@return?the?string
?*/
public?String?responseSign(String?wechatpayTimestamp,?String?wechatpayNonce,?String?body)?{
????return?Stream.of(wechatpayTimestamp,?wechatpayNonce,?body)
????????????.collect(Collectors.joining("\n",?"",?"\n"));
}
驗證簽名
待驗證的簽名從響應(yīng)頭中的Wechatpay-Signature字段中獲取,我們使用微信支付平臺公鑰對驗簽名串和簽名進(jìn)行SHA256 with RSA簽名驗證。
???//?構(gòu)造驗簽名串
????????final?String?signatureStr?=?responseSign(wechatpayTimestamp,?wechatpayNonce,?body);
???//?加載SHA256withRSA簽名器
????????Signature?signer?=?Signature.getInstance("SHA256withRSA");
??//?用微信平臺公鑰對簽名器進(jìn)行初始化
????????signer.initVerify(certificate);
???//?把我們構(gòu)造的驗簽名串更新到簽名器中
????????signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
???//?把請求頭中微信服務(wù)器返回的簽名用Base64解碼?并使用簽名器進(jìn)行驗證
????????boolean?result?=?signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
完整的驗簽代碼
/**
?*?我方對響應(yīng)驗簽,和應(yīng)答簽名做比較,使用微信平臺證書.
?*
?*?@param?wechatpaySerial????response.headers['Wechatpay-Serial']????當(dāng)前使用的微信平臺證書序列號
?*?@param?wechatpaySignature?response.headers['Wechatpay-Signature']???微信平臺簽名
?*?@param?wechatpayTimestamp?response.headers['Wechatpay-Timestamp']??微信服務(wù)器的時間戳
?*?@param?wechatpayNonce?????response.headers['Wechatpay-Nonce']???微信服務(wù)器提供的隨機(jī)串
?*?@param?body???????????????response.body?微信服務(wù)器的響應(yīng)體
?*?@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. 總結(jié)
驗簽通過就說明我們請求的響應(yīng)來自微信服務(wù)器就可以針對結(jié)果進(jìn)行對應(yīng)的邏輯處理了,微信支付 API 無論是 V2 還是 V3 都包含了使用Api 證書對請求進(jìn)行加簽,對響應(yīng)結(jié)果進(jìn)行驗簽的流程,十分考驗對密碼摘要算法的使用,其它無非就是組織參數(shù)調(diào)用 Http 請求。如果你能夠掌握這一能力就會在面試中和工作中占到優(yōu)勢。好了今天分享就到這里,關(guān)注下面的公眾號:碼農(nóng)小胖哥?獲取更多實用的編程干貨。

往期推薦
﹀
﹀
﹀
深度內(nèi)容
推薦加入
最近熱門內(nèi)容回顧? ?#技術(shù)人系列

