Skip to content

Commit

Permalink
feat: 新增 ProxyFactory,支持动态获取 HttpHost (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
ramzeng authored Oct 15, 2024
1 parent aff46bf commit 37f63ea
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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);
/**
* 证书下载地址
*/
Expand All @@ -54,65 +56,18 @@ public class CertificatesManager {
private volatile static CertificatesManager instance = null;
private ConcurrentHashMap<String, byte[]> apiV3Keys = new ConcurrentHashMap<>();

private HttpProxyFactory proxyFactory;
private HttpHost proxy;

private ConcurrentHashMap<String, ConcurrentHashMap<BigInteger, X509Certificate>> certificates = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, ConcurrentHashMap<BigInteger, X509Certificate>> certificates =
new ConcurrentHashMap<>();

private ConcurrentHashMap<String, Credentials> credentialsMap = new ConcurrentHashMap<>();
/**
* 执行定时更新平台证书的线程池
*/
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<BigInteger, X509Certificate> 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() {
}

Expand Down Expand Up @@ -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;
}

/**
* 停止自动更新平台证书,停止后无法再重新启动
*/
Expand Down Expand Up @@ -236,7 +204,6 @@ public Verifier getVerifier(String merchantId) throws NotFoundException {
return new DefaultVerifier(merchantId);
}


private void beginScheduleUpdate() {
executor = new SafeSingleScheduleExecutor();
Runnable runnable = () -> {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<BigInteger, X509Certificate> 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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
}
}

0 comments on commit 37f63ea

Please sign in to comment.