Skip to content

Commit b0d6fc7

Browse files
author
ramzeng
committed
feat: 新增 ProxyFactory,支持动态获取 HttpHost
1 parent aff46bf commit b0d6fc7

File tree

3 files changed

+116
-61
lines changed

3 files changed

+116
-61
lines changed

src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java

+74-57
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
1111
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
1212
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
13+
import com.wechat.pay.contrib.apache.httpclient.proxy.HttpProxyFactory;
1314
import com.wechat.pay.contrib.apache.httpclient.util.CertSerializeUtil;
1415
import java.io.IOException;
1516
import java.math.BigInteger;
@@ -25,14 +26,15 @@
2526
import java.util.Base64;
2627
import java.util.Map;
2728
import java.util.NoSuchElementException;
29+
import java.util.Objects;
2830
import java.util.concurrent.ConcurrentHashMap;
2931
import java.util.concurrent.ScheduledExecutorService;
3032
import java.util.concurrent.TimeUnit;
33+
import org.apache.http.HttpHost;
3134
import org.apache.http.client.methods.CloseableHttpResponse;
3235
import org.apache.http.client.methods.HttpGet;
3336
import org.apache.http.impl.client.CloseableHttpClient;
3437
import org.apache.http.util.EntityUtils;
35-
import org.apache.http.HttpHost;
3638
import org.slf4j.Logger;
3739
import org.slf4j.LoggerFactory;
3840

@@ -44,8 +46,8 @@
4446
*/
4547
public class CertificatesManager {
4648

47-
private static final Logger log = LoggerFactory.getLogger(CertificatesManager.class);
4849
protected static final int UPDATE_INTERVAL_MINUTE = 1440;
50+
private static final Logger log = LoggerFactory.getLogger(CertificatesManager.class);
4951
/**
5052
* 证书下载地址
5153
*/
@@ -54,65 +56,18 @@ public class CertificatesManager {
5456
private volatile static CertificatesManager instance = null;
5557
private ConcurrentHashMap<String, byte[]> apiV3Keys = new ConcurrentHashMap<>();
5658

59+
private HttpProxyFactory proxyFactory;
5760
private HttpHost proxy;
5861

59-
private ConcurrentHashMap<String, ConcurrentHashMap<BigInteger, X509Certificate>> certificates = new ConcurrentHashMap<>();
62+
private ConcurrentHashMap<String, ConcurrentHashMap<BigInteger, X509Certificate>> certificates =
63+
new ConcurrentHashMap<>();
6064

6165
private ConcurrentHashMap<String, Credentials> credentialsMap = new ConcurrentHashMap<>();
6266
/**
6367
* 执行定时更新平台证书的线程池
6468
*/
6569
private ScheduledExecutorService executor;
6670

67-
/**
68-
* 内部验签器
69-
*/
70-
private class DefaultVerifier implements Verifier {
71-
72-
private String merchantId;
73-
74-
private DefaultVerifier(String merchantId) {
75-
this.merchantId = merchantId;
76-
}
77-
78-
@Override
79-
public boolean verify(String serialNumber, byte[] message, String signature) {
80-
if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) {
81-
throw new IllegalArgumentException("serialNumber或message或signature为空");
82-
}
83-
BigInteger serialNumber16Radix = new BigInteger(serialNumber, 16);
84-
ConcurrentHashMap<BigInteger, X509Certificate> merchantCertificates = certificates.get(merchantId);
85-
X509Certificate certificate = merchantCertificates.get(serialNumber16Radix);
86-
if (certificate == null) {
87-
log.error("商户证书为空,serialNumber:{}", serialNumber);
88-
return false;
89-
}
90-
try {
91-
Signature sign = Signature.getInstance("SHA256withRSA");
92-
sign.initVerify(certificate);
93-
sign.update(message);
94-
return sign.verify(Base64.getDecoder().decode(signature));
95-
} catch (NoSuchAlgorithmException e) {
96-
throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
97-
} catch (SignatureException e) {
98-
throw new RuntimeException("签名验证过程发生了错误", e);
99-
} catch (InvalidKeyException e) {
100-
throw new RuntimeException("无效的证书", e);
101-
}
102-
}
103-
104-
@Override
105-
public X509Certificate getValidCertificate() {
106-
X509Certificate certificate;
107-
try {
108-
certificate = CertificatesManager.this.getLatestCertificate(merchantId);
109-
} catch (NotFoundException e) {
110-
throw new NoSuchElementException("没有有效的微信支付平台证书");
111-
}
112-
return certificate;
113-
}
114-
}
115-
11671
private CertificatesManager() {
11772
}
11873

@@ -162,14 +117,27 @@ public synchronized void putMerchant(String merchantId, Credentials credentials,
162117
}
163118

164119
/***
165-
* 代理配置
166-
*
167-
* @param proxy 代理host
168-
**/
120+
* 代理配置
121+
*
122+
* @param proxy 代理host
123+
**/
169124
public synchronized void setProxy(HttpHost proxy) {
170125
this.proxy = proxy;
171126
}
172127

128+
/**
129+
* 设置代理工厂
130+
*
131+
* @param proxyFactory 代理工厂
132+
*/
133+
public synchronized void setProxyFactory(HttpProxyFactory proxyFactory) {
134+
this.proxyFactory = proxyFactory;
135+
}
136+
137+
public synchronized HttpHost resolveProxy() {
138+
return Objects.nonNull(proxyFactory) ? proxyFactory.buildHttpProxy() : proxy;
139+
}
140+
173141
/**
174142
* 停止自动更新平台证书,停止后无法再重新启动
175143
*/
@@ -236,7 +204,6 @@ public Verifier getVerifier(String merchantId) throws NotFoundException {
236204
return new DefaultVerifier(merchantId);
237205
}
238206

239-
240207
private void beginScheduleUpdate() {
241208
executor = new SafeSingleScheduleExecutor();
242209
Runnable runnable = () -> {
@@ -265,6 +232,7 @@ private void beginScheduleUpdate() {
265232
*/
266233
private synchronized void downloadAndUpdateCert(String merchantId, Verifier verifier, Credentials credentials,
267234
byte[] apiV3Key) throws HttpCodeException, IOException, GeneralSecurityException {
235+
proxy = resolveProxy();
268236
try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
269237
.withCredentials(credentials)
270238
.withValidator(verifier == null ? (response) -> true
@@ -324,4 +292,53 @@ private void updateCertificates() {
324292
}
325293
}
326294
}
295+
296+
/**
297+
* 内部验签器
298+
*/
299+
private class DefaultVerifier implements Verifier {
300+
301+
private String merchantId;
302+
303+
private DefaultVerifier(String merchantId) {
304+
this.merchantId = merchantId;
305+
}
306+
307+
@Override
308+
public boolean verify(String serialNumber, byte[] message, String signature) {
309+
if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) {
310+
throw new IllegalArgumentException("serialNumber或message或signature为空");
311+
}
312+
BigInteger serialNumber16Radix = new BigInteger(serialNumber, 16);
313+
ConcurrentHashMap<BigInteger, X509Certificate> merchantCertificates = certificates.get(merchantId);
314+
X509Certificate certificate = merchantCertificates.get(serialNumber16Radix);
315+
if (certificate == null) {
316+
log.error("商户证书为空,serialNumber:{}", serialNumber);
317+
return false;
318+
}
319+
try {
320+
Signature sign = Signature.getInstance("SHA256withRSA");
321+
sign.initVerify(certificate);
322+
sign.update(message);
323+
return sign.verify(Base64.getDecoder().decode(signature));
324+
} catch (NoSuchAlgorithmException e) {
325+
throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
326+
} catch (SignatureException e) {
327+
throw new RuntimeException("签名验证过程发生了错误", e);
328+
} catch (InvalidKeyException e) {
329+
throw new RuntimeException("无效的证书", e);
330+
}
331+
}
332+
333+
@Override
334+
public X509Certificate getValidCertificate() {
335+
X509Certificate certificate;
336+
try {
337+
certificate = CertificatesManager.this.getLatestCertificate(merchantId);
338+
} catch (NotFoundException e) {
339+
throw new NoSuchElementException("没有有效的微信支付平台证书");
340+
}
341+
return certificate;
342+
}
343+
}
327344
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.wechat.pay.contrib.apache.httpclient.proxy;
2+
3+
import org.apache.http.HttpHost;
4+
5+
/**
6+
* HttpProxyFactory 代理工厂
7+
*
8+
* @author ramzeng
9+
*/
10+
public interface HttpProxyFactory {
11+
12+
/**
13+
* 构建代理
14+
*
15+
* @return 代理
16+
*/
17+
HttpHost buildHttpProxy();
18+
}

src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java

+24-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
1111
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
1212
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
13+
import com.wechat.pay.contrib.apache.httpclient.proxy.HttpProxyFactory;
1314
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
1415
import java.io.File;
1516
import java.io.FileInputStream;
@@ -20,13 +21,14 @@
2021
import java.security.PrivateKey;
2122
import org.apache.commons.codec.digest.DigestUtils;
2223
import org.apache.http.HttpEntity;
24+
import org.apache.http.HttpHost;
2325
import org.apache.http.client.methods.CloseableHttpResponse;
2426
import org.apache.http.client.methods.HttpGet;
2527
import org.apache.http.client.utils.URIBuilder;
2628
import org.apache.http.impl.client.CloseableHttpClient;
2729
import org.apache.http.util.EntityUtils;
28-
import org.apache.http.HttpHost;
2930
import org.junit.After;
31+
import org.junit.Assert;
3032
import org.junit.Before;
3133
import org.junit.Test;
3234

@@ -38,11 +40,10 @@ public class CertificatesManagerTest {
3840
private static final String merchantId = ""; // 商户号
3941
private static final String merchantSerialNumber = ""; // 商户证书序列号
4042
private static final String apiV3Key = ""; // API V3密钥
41-
private CloseableHttpClient httpClient;
43+
private static final HttpHost proxy = null;
4244
CertificatesManager certificatesManager;
4345
Verifier verifier;
44-
45-
private static final HttpHost proxy = null;
46+
private CloseableHttpClient httpClient;
4647

4748
@Before
4849
public void setup() throws Exception {
@@ -137,4 +138,23 @@ public void uploadFileTest() throws Exception {
137138
}
138139
}
139140
}
141+
142+
@Test
143+
public void proxyFactoryTest() {
144+
CertificatesManager certificatesManager = CertificatesManager.getInstance();
145+
Assert.assertEquals(certificatesManager.resolveProxy(), proxy);
146+
certificatesManager.setProxyFactory(new MockHttpProxyFactory());
147+
HttpHost httpProxy = certificatesManager.resolveProxy();
148+
Assert.assertNotEquals(httpProxy, proxy);
149+
Assert.assertEquals(httpProxy.getHostName(), "127.0.0.1");
150+
Assert.assertEquals(httpProxy.getPort(), 1087);
151+
}
152+
153+
private static class MockHttpProxyFactory implements HttpProxyFactory {
154+
155+
@Override
156+
public HttpHost buildHttpProxy() {
157+
return new HttpHost("127.0.0.1", 1087);
158+
}
159+
}
140160
}

0 commit comments

Comments
 (0)