🎨 #3854 【微信支付】修复公钥模式下平台证书自动更新导致的初始化失败问题
This commit is contained in:
@@ -420,7 +420,13 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
|
|||||||
return wxPayConfig.getPublicKeyId();
|
return wxPayConfig.getPublicKeyId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
|
try {
|
||||||
|
return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to get certificate serial number: {}", e.getMessage());
|
||||||
|
// 返回空字符串而不是抛出异常,让请求继续进行,由微信服务器判断是否需要Wechatpay-Serial
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logRequestAndResponse(String url, String requestStr, String responseStr) {
|
private void logRequestAndResponse(String url, String requestStr, String responseStr) {
|
||||||
|
|||||||
@@ -398,7 +398,13 @@ public class WxPayServiceHttpComponentsImpl extends BaseWxPayServiceImpl {
|
|||||||
return wxPayConfig.getPublicKeyId();
|
return wxPayConfig.getPublicKeyId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
|
try {
|
||||||
|
return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to get certificate serial number: {}", e.getMessage());
|
||||||
|
// 返回空字符串而不是抛出异常,让请求继续进行,由微信服务器判断是否需要Wechatpay-Serial
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logRequestAndResponse(String url, String requestStr, String responseStr) {
|
private void logRequestAndResponse(String url, String requestStr, String responseStr) {
|
||||||
|
|||||||
@@ -109,18 +109,24 @@ public class AutoUpdateCertificatesVerifier implements Verifier {
|
|||||||
this.minutesInterval = minutesInterval;
|
this.minutesInterval = minutesInterval;
|
||||||
this.payBaseUrl = payBaseUrl;
|
this.payBaseUrl = payBaseUrl;
|
||||||
this.wxPayHttpProxy = wxPayHttpProxy;
|
this.wxPayHttpProxy = wxPayHttpProxy;
|
||||||
//构造时更新证书
|
//构造时尝试更新证书,但失败时不抛出异常,避免影响公钥模式的使用
|
||||||
try {
|
try {
|
||||||
autoUpdateCert();
|
autoUpdateCert();
|
||||||
instant = Instant.now();
|
instant = Instant.now();
|
||||||
} catch (IOException | GeneralSecurityException e) {
|
} catch (IOException | GeneralSecurityException e) {
|
||||||
throw new WxRuntimeException(e);
|
log.warn("Auto update cert failed during initialization, will retry later, exception = {}", e.getMessage());
|
||||||
|
// 设置 instant 为 null,后续每次使用时都会尝试下载证书直到成功
|
||||||
|
instant = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verify(String serialNumber, byte[] message, String signature) {
|
public boolean verify(String serialNumber, byte[] message, String signature) {
|
||||||
checkAndAutoUpdateCert();
|
checkAndAutoUpdateCert();
|
||||||
|
if (verifier == null) {
|
||||||
|
log.warn("No valid certificate available for verification");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return verifier.verify(serialNumber, message, signature);
|
return verifier.verify(serialNumber, message, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +226,9 @@ public class AutoUpdateCertificatesVerifier implements Verifier {
|
|||||||
@Override
|
@Override
|
||||||
public X509Certificate getValidCertificate() {
|
public X509Certificate getValidCertificate() {
|
||||||
checkAndAutoUpdateCert();
|
checkAndAutoUpdateCert();
|
||||||
|
if (verifier == null) {
|
||||||
|
throw new WxRuntimeException("No valid certificate available, please check your configuration or use fullPublicKeyModel mode");
|
||||||
|
}
|
||||||
return verifier.getValidCertificate();
|
return verifier.getValidCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.github.binarywang.wxpay.v3.auth;
|
||||||
|
|
||||||
|
import org.testng.annotations.BeforeMethod;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试公钥模式下 AutoUpdateCertificatesVerifier 的健壮性
|
||||||
|
*
|
||||||
|
* @author copilot
|
||||||
|
*/
|
||||||
|
public class AutoUpdateCertificatesVerifierPublicKeyModeTest {
|
||||||
|
|
||||||
|
private String invalidMchId;
|
||||||
|
private String invalidApiV3Key;
|
||||||
|
private String invalidCertSerialNo;
|
||||||
|
private String payBaseUrl;
|
||||||
|
private WxPayCredentials credentials;
|
||||||
|
|
||||||
|
@BeforeMethod
|
||||||
|
public void setUp() {
|
||||||
|
// 使用无效的配置,模拟证书下载失败的场景
|
||||||
|
invalidMchId = "invalid_mch_id";
|
||||||
|
invalidApiV3Key = "invalid_api_v3_key_must_be_32_b";
|
||||||
|
invalidCertSerialNo = "invalid_serial_no";
|
||||||
|
payBaseUrl = "https://api.mch.weixin.qq.com";
|
||||||
|
|
||||||
|
credentials = new WxPayCredentials(
|
||||||
|
invalidMchId,
|
||||||
|
new PrivateKeySigner(invalidCertSerialNo, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试当证书下载失败时,构造函数不应该抛出异常
|
||||||
|
* 这是为了支持公钥模式下的场景,在公钥模式下商户可能没有平台证书
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testConstructorShouldNotThrowExceptionWhenCertDownloadFails() {
|
||||||
|
// 构造函数应该不抛出异常,即使证书下载失败
|
||||||
|
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
|
||||||
|
credentials,
|
||||||
|
invalidApiV3Key.getBytes(StandardCharsets.UTF_8),
|
||||||
|
60,
|
||||||
|
payBaseUrl,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
// 如果没有抛出异常,测试通过
|
||||||
|
assertNotNull(verifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试当没有有效证书时,verify 方法应该返回 false 而不是抛出异常
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testVerifyShouldReturnFalseWhenNoCertificateAvailable() {
|
||||||
|
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
|
||||||
|
credentials,
|
||||||
|
invalidApiV3Key.getBytes(StandardCharsets.UTF_8),
|
||||||
|
60,
|
||||||
|
payBaseUrl,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
// verify 方法应该返回 false,而不是抛出异常
|
||||||
|
boolean result = verifier.verify("test_serial", "test_message".getBytes(), "test_signature");
|
||||||
|
assertFalse(result, "当没有有效证书时,verify 应该返回 false");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试当没有有效证书时,getValidCertificate 方法应该抛出有意义的异常
|
||||||
|
*/
|
||||||
|
@Test(expectedExceptions = me.chanjar.weixin.common.error.WxRuntimeException.class,
|
||||||
|
expectedExceptionsMessageRegExp = ".*No valid certificate available.*")
|
||||||
|
public void testGetValidCertificateShouldThrowMeaningfulException() {
|
||||||
|
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
|
||||||
|
credentials,
|
||||||
|
invalidApiV3Key.getBytes(StandardCharsets.UTF_8),
|
||||||
|
60,
|
||||||
|
payBaseUrl,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
// 应该抛出有意义的异常
|
||||||
|
X509Certificate certificate = verifier.getValidCertificate();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user