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請求。如果你能夠掌握這一能力就會在面試中和工作中占到優勢。好了今天分享就到這里,多多關注: 碼農小胖哥 獲取更多實用的編程干貨。
關注公眾號:Felordcn 獲取更多資訊