From 37f63ea8f984ce6557c3c8941bd195c7df2756ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=98=AF=E7=84=B6?= Date: Tue, 15 Oct 2024 14:31:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20ProxyFactory?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=20HttpHost=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../httpclient/cert/CertificatesManager.java | 131 ++++++++++-------- .../httpclient/proxy/HttpProxyFactory.java | 18 +++ .../httpclient/CertificatesManagerTest.java | 28 +++- 3 files changed, 116 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/wechat/pay/contrib/apache/httpclient/proxy/HttpProxyFactory.java diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java index 4b69897..00c123f 100644 --- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java +++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java @@ -10,6 +10,7 @@ import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException; import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException; +import com.wechat.pay.contrib.apache.httpclient.proxy.HttpProxyFactory; import com.wechat.pay.contrib.apache.httpclient.util.CertSerializeUtil; import java.io.IOException; import java.math.BigInteger; @@ -25,14 +26,15 @@ import java.util.Base64; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import org.apache.http.HttpHost; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; -import org.apache.http.HttpHost; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,8 +46,8 @@ */ public class CertificatesManager { - private static final Logger log = LoggerFactory.getLogger(CertificatesManager.class); protected static final int UPDATE_INTERVAL_MINUTE = 1440; + private static final Logger log = LoggerFactory.getLogger(CertificatesManager.class); /** * 证书下载地址 */ @@ -54,9 +56,11 @@ public class CertificatesManager { private volatile static CertificatesManager instance = null; private ConcurrentHashMap apiV3Keys = new ConcurrentHashMap<>(); + private HttpProxyFactory proxyFactory; private HttpHost proxy; - private ConcurrentHashMap> certificates = new ConcurrentHashMap<>(); + private ConcurrentHashMap> certificates = + new ConcurrentHashMap<>(); private ConcurrentHashMap credentialsMap = new ConcurrentHashMap<>(); /** @@ -64,55 +68,6 @@ public class CertificatesManager { */ private ScheduledExecutorService executor; - /** - * 内部验签器 - */ - private class DefaultVerifier implements Verifier { - - private String merchantId; - - private DefaultVerifier(String merchantId) { - this.merchantId = merchantId; - } - - @Override - public boolean verify(String serialNumber, byte[] message, String signature) { - if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) { - throw new IllegalArgumentException("serialNumber或message或signature为空"); - } - BigInteger serialNumber16Radix = new BigInteger(serialNumber, 16); - ConcurrentHashMap merchantCertificates = certificates.get(merchantId); - X509Certificate certificate = merchantCertificates.get(serialNumber16Radix); - if (certificate == null) { - log.error("商户证书为空,serialNumber:{}", serialNumber); - return false; - } - try { - Signature sign = Signature.getInstance("SHA256withRSA"); - sign.initVerify(certificate); - sign.update(message); - return sign.verify(Base64.getDecoder().decode(signature)); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); - } catch (SignatureException e) { - throw new RuntimeException("签名验证过程发生了错误", e); - } catch (InvalidKeyException e) { - throw new RuntimeException("无效的证书", e); - } - } - - @Override - public X509Certificate getValidCertificate() { - X509Certificate certificate; - try { - certificate = CertificatesManager.this.getLatestCertificate(merchantId); - } catch (NotFoundException e) { - throw new NoSuchElementException("没有有效的微信支付平台证书"); - } - return certificate; - } - } - private CertificatesManager() { } @@ -162,14 +117,27 @@ public synchronized void putMerchant(String merchantId, Credentials credentials, } /*** - * 代理配置 - * - * @param proxy 代理host - **/ + * 代理配置 + * + * @param proxy 代理host + **/ public synchronized void setProxy(HttpHost proxy) { this.proxy = proxy; } + /** + * 设置代理工厂 + * + * @param proxyFactory 代理工厂 + */ + public synchronized void setProxyFactory(HttpProxyFactory proxyFactory) { + this.proxyFactory = proxyFactory; + } + + public synchronized HttpHost resolveProxy() { + return Objects.nonNull(proxyFactory) ? proxyFactory.buildHttpProxy() : proxy; + } + /** * 停止自动更新平台证书,停止后无法再重新启动 */ @@ -236,7 +204,6 @@ public Verifier getVerifier(String merchantId) throws NotFoundException { return new DefaultVerifier(merchantId); } - private void beginScheduleUpdate() { executor = new SafeSingleScheduleExecutor(); Runnable runnable = () -> { @@ -265,6 +232,7 @@ private void beginScheduleUpdate() { */ private synchronized void downloadAndUpdateCert(String merchantId, Verifier verifier, Credentials credentials, byte[] apiV3Key) throws HttpCodeException, IOException, GeneralSecurityException { + proxy = resolveProxy(); try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create() .withCredentials(credentials) .withValidator(verifier == null ? (response) -> true @@ -324,4 +292,53 @@ private void updateCertificates() { } } } + + /** + * 内部验签器 + */ + private class DefaultVerifier implements Verifier { + + private String merchantId; + + private DefaultVerifier(String merchantId) { + this.merchantId = merchantId; + } + + @Override + public boolean verify(String serialNumber, byte[] message, String signature) { + if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) { + throw new IllegalArgumentException("serialNumber或message或signature为空"); + } + BigInteger serialNumber16Radix = new BigInteger(serialNumber, 16); + ConcurrentHashMap merchantCertificates = certificates.get(merchantId); + X509Certificate certificate = merchantCertificates.get(serialNumber16Radix); + if (certificate == null) { + log.error("商户证书为空,serialNumber:{}", serialNumber); + return false; + } + try { + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initVerify(certificate); + sign.update(message); + return sign.verify(Base64.getDecoder().decode(signature)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); + } catch (SignatureException e) { + throw new RuntimeException("签名验证过程发生了错误", e); + } catch (InvalidKeyException e) { + throw new RuntimeException("无效的证书", e); + } + } + + @Override + public X509Certificate getValidCertificate() { + X509Certificate certificate; + try { + certificate = CertificatesManager.this.getLatestCertificate(merchantId); + } catch (NotFoundException e) { + throw new NoSuchElementException("没有有效的微信支付平台证书"); + } + return certificate; + } + } } diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/proxy/HttpProxyFactory.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/proxy/HttpProxyFactory.java new file mode 100644 index 0000000..59c7f22 --- /dev/null +++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/proxy/HttpProxyFactory.java @@ -0,0 +1,18 @@ +package com.wechat.pay.contrib.apache.httpclient.proxy; + +import org.apache.http.HttpHost; + +/** + * HttpProxyFactory 代理工厂 + * + * @author ramzeng + */ +public interface HttpProxyFactory { + + /** + * 构建代理 + * + * @return 代理 + */ + HttpHost buildHttpProxy(); +} diff --git a/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java b/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java index 3db8659..464af3a 100644 --- a/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java +++ b/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java @@ -10,6 +10,7 @@ import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager; +import com.wechat.pay.contrib.apache.httpclient.proxy.HttpProxyFactory; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import java.io.File; import java.io.FileInputStream; @@ -20,13 +21,14 @@ import java.security.PrivateKey; import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; -import org.apache.http.HttpHost; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -38,11 +40,10 @@ public class CertificatesManagerTest { private static final String merchantId = ""; // 商户号 private static final String merchantSerialNumber = ""; // 商户证书序列号 private static final String apiV3Key = ""; // API V3密钥 - private CloseableHttpClient httpClient; + private static final HttpHost proxy = null; CertificatesManager certificatesManager; Verifier verifier; - - private static final HttpHost proxy = null; + private CloseableHttpClient httpClient; @Before public void setup() throws Exception { @@ -137,4 +138,23 @@ public void uploadFileTest() throws Exception { } } } + + @Test + public void proxyFactoryTest() { + CertificatesManager certificatesManager = CertificatesManager.getInstance(); + Assert.assertEquals(certificatesManager.resolveProxy(), proxy); + certificatesManager.setProxyFactory(new MockHttpProxyFactory()); + HttpHost httpProxy = certificatesManager.resolveProxy(); + Assert.assertNotEquals(httpProxy, proxy); + Assert.assertEquals(httpProxy.getHostName(), "127.0.0.1"); + Assert.assertEquals(httpProxy.getPort(), 1087); + } + + private static class MockHttpProxyFactory implements HttpProxyFactory { + + @Override + public HttpHost buildHttpProxy() { + return new HttpHost("127.0.0.1", 1087); + } + } }