Skip to content

feat: 新增 ProxyFactory,支持动态获取 HttpHost #214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
}
Loading