1. 前言
牢記一句話:公鑰加密,私鑰解密泼疑;私鑰加簽控汉,公鑰驗(yàn)簽。
微信支付V3版本前兩篇分別講了如何對請求做簽名和如何獲取并刷新微信平臺(tái)公鑰敏晤,本篇將繼續(xù)展開如何對微信支付響應(yīng)結(jié)果的驗(yàn)簽。
2. 為什么要對響應(yīng)驗(yàn)簽
微信支付會(huì)在回調(diào)的HTTP頭部中包括回調(diào)報(bào)文的簽名鸡岗。商戶必須驗(yàn)證響應(yīng)的簽名鸳劳,保證響應(yīng)確實(shí)來自微信支付服務(wù)器,避免中間人攻擊宝鼓。而驗(yàn)證響應(yīng)簽名除了需要微信平臺(tái)的公鑰外還需要從請求頭的其它參數(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"}
檢查平臺(tái)證書序列號(hào)
微信支付響應(yīng)的時(shí)候會(huì)攜帶一個(gè)微信平臺(tái)證書序列號(hào),從響應(yīng)頭中的Wechatpay-Serial
字段中獲取值愚铡,用來提示我們要使用該序列號(hào)的證書來進(jìn)行驗(yàn)簽蛉签,如果不存在就需要我們刷新證書,而上一文我們將平臺(tái)證書序列號(hào)和證書以鍵值對存在HashMap
中沥寥,我們只需要檢查是否存在即可碍舍,不存在就刷新。
構(gòu)造驗(yàn)簽名串
從響應(yīng)結(jié)果中獲取對應(yīng)下面方法的三個(gè)參數(shù)就可以構(gòu)造出驗(yàn)簽名串营曼。
/**
* 構(gòu)造驗(yàn)簽名串.
*
* @param wechatpayTimestamp HTTP頭 Wechatpay-Timestamp 中的應(yīng)答時(shí)間戳乒验。
* @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àn)證簽名
待驗(yàn)證的簽名從響應(yīng)頭中的Wechatpay-Signature
字段中獲取,我們使用微信支付平臺(tái)公鑰對驗(yàn)簽名串和簽名進(jìn)行SHA256 with RSA簽名驗(yàn)證蒂阱。
// 構(gòu)造驗(yàn)簽名串
final String signatureStr = responseSign(wechatpayTimestamp, wechatpayNonce, body);
// 加載SHA256withRSA簽名器
Signature signer = Signature.getInstance("SHA256withRSA");
// 用微信平臺(tái)公鑰對簽名器進(jìn)行初始化
signer.initVerify(certificate);
// 把我們構(gòu)造的驗(yàn)簽名串更新到簽名器中
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
// 把請求頭中微信服務(wù)器返回的簽名用Base64解碼 并使用簽名器進(jìn)行驗(yàn)證
boolean result = signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
完整的驗(yàn)簽代碼
/**
* 我方對響應(yīng)驗(yàn)簽锻全,和應(yīng)答簽名做比較狂塘,使用微信平臺(tái)證書.
*
* @param wechatpaySerial response.headers['Wechatpay-Serial'] 當(dāng)前使用的微信平臺(tái)證書序列號(hào)
* @param wechatpaySignature response.headers['Wechatpay-Signature'] 微信平臺(tái)簽名
* @param wechatpayTimestamp response.headers['Wechatpay-Timestamp'] 微信服務(wù)器的時(shí)間戳
* @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
平臺(tái)證書容器可參考上一篇文章。
3. 總結(jié)
驗(yàn)簽通過就說明我們請求的響應(yīng)來自微信服務(wù)器就可以針對結(jié)果進(jìn)行對應(yīng)的邏輯處理了鳄厌,微信支付API無論是V2還是V3都包含了使用Api證書對請求進(jìn)行加簽荞胡,對響應(yīng)結(jié)果進(jìn)行驗(yàn)簽的流程,十分考驗(yàn)對密碼摘要算法的使用了嚎,其它就是組織參數(shù)調(diào)用Http請求泪漂。如果你能夠掌握這一能力就會(huì)在面試中和工作中占到優(yōu)勢。好了今天分享就到這里歪泳,多多關(guān)注: 碼農(nóng)小胖哥 獲取更多實(shí)用的編程干貨萝勤。
關(guān)注公眾號(hào):碼農(nóng)小胖哥,獲取更多資訊