Skip to content

Commit

Permalink
Merge pull request #88 from lianup/master
Browse files Browse the repository at this point in the history
增加平台证书管理器
  • Loading branch information
Eric-Lee-Handyman authored Nov 29, 2021
2 parents bd5f22b + 4203017 commit 58d3de5
Show file tree
Hide file tree
Showing 14 changed files with 530 additions and 36 deletions.
41 changes: 23 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@

## 项目状态

当前版本`0.2.3`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
当前版本`0.3.0`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。

## 升级指引

版本`0.3.0`提供了更可靠的[定时更新平台证书功能](#定时更新平台证书功能)。若你使用了自动更新证书功能,推荐升级并使用新的`ScheduledUpdateCertificatesVerifier`替换`AutoUpdateCertificatesVerifier`

注:`Verifier`接口新增了`getLatestCertificate()`方法。若你通过实现`Verifier`接口自定义了验签器,升级后需实现该方法。

## 环境要求

Expand All @@ -23,7 +29,7 @@
在你的`build.gradle`文件中加入如下的依赖

```groovy
implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.2.3'
implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.3.0'
```

### Maven
Expand All @@ -33,7 +39,7 @@ implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.2.3'
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.3</version>
<version>0.3.0</version>
</dependency>
```

Expand All @@ -58,18 +64,18 @@ WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
HttpClient httpClient = builder.build();
CloseableHttpClient httpClient = builder.build();

// 后面跟使用Apache HttpClient一样
HttpResponse response = httpClient.execute(...);
ClosableHttpResponse response = httpClient.execute(...);
```

参数说明:

+ `merchantId`商户号。
+ `merchantSerialNumber`商户API证书的证书序列号。
+ `merchantPrivateKey`商户API私钥,如何加载商户API私钥请看[常见问题](#如何加载商户私钥)
+ `wechatpayCertificates`微信支付平台证书。你也可以使用后面章节提到的“[自动更新证书功能](#自动更新证书功能)”,而不需要关心平台证书的来龙去脉。
+ `wechatpayCertificates`微信支付平台证书。你也可以使用后面章节提到的“[定时更新平台证书功能](#定时更新平台证书功能)”,而不需要关心平台证书的来龙去脉。

### 示例:获取平台证书

Expand Down Expand Up @@ -176,36 +182,35 @@ WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withWechatPay(wechatpayCertificates);
```

## 自动更新证书功能
## 定时更新平台证书功能

版本`>=0.1.5`可使用 AutoUpdateCertificatesVerifier 类替代默认的验签器。它会在构造时自动下载商户对应的[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu),并每隔一段时间(默认为1个小时)更新证书。

参数说明:`apiV3Key`是String格式的API v3密钥。
版本>=`0.3.0`可使用 ScheduledUpdateCertificatesVerifier 类替代默认的验签器。它会定时下载和更新商户对应的[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu) (默认为1小时)。

示例代码:

```java
//不需要传入微信支付证书了
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
apiV3Key.getBytes("utf-8"));
// 使用定时更新的签名验证器,不需要传入证书
verifier = new ScheduledUpdateCertificatesVerifier(
new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
apiV3Key.getBytes(StandardCharsets.UTF_8));

WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
HttpClient httpClient = builder.build();
CloseableHttpClient httpClient = builder.build();

// 后面跟使用Apache HttpClient一样
HttpResponse response = httpClient.execute(...);
CloseableHttpResponse response = httpClient.execute(...);
```

### 风险

因为不需要传入微信支付平台证书,AutoUpdateCertificatesVerifier 在首次更新证书时**不会验签**,也就无法确认应答身份,可能导致下载错误的证书。
因为不需要传入微信支付平台证书,ScheduledUpdateCertificatesVerifier 在首次更新证书时**不会验签**,也就无法确认应答身份,可能导致下载错误的证书。

但下载时会通过 **HTTPS****AES 对称加密**来保证证书安全,所以可以认为,在使用官方 JDK、且 APIv3 密钥不泄露的情况下,AutoUpdateCertificatesVerifier**安全**的。
但下载时会通过 **HTTPS****AES 对称加密**来保证证书安全,所以可以认为,在使用官方 JDK、且 APIv3 密钥不泄露的情况下,ScheduledUpdateCertificatesVerifier**安全**的。

## 敏感信息加解密

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group 'com.github.wechatpay-apiv3'
version '0.2.3'
version '0.3.0'

sourceCompatibility = 1.8
targetCompatibility = 1.8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public WechatPayHttpClientBuilder withWechatPay(List<X509Certificate> certificat

/**
* Please use {@link #withWechatPay(List)} instead
*
* @param certificates 平台证书list
* @return 具有验证器的builder
*/
@SuppressWarnings("SpellCheckingInspection")
@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@

/**
* 在原有CertificatesVerifier基础上,增加自动更新证书功能
* 该类已废弃,请使用ScheduledUpdateCertificatesVerifier
*
* @author xy-peng
*/
@Deprecated
public class AutoUpdateCertificatesVerifier implements Verifier {

protected static final Logger log = LoggerFactory.getLogger(AutoUpdateCertificatesVerifier.class);
Expand Down Expand Up @@ -72,11 +74,6 @@ public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key,
}
}

@Override
public X509Certificate getValidCertificate() {
return verifier.getValidCertificate();
}

@Override
public boolean verify(String serialNumber, byte[] message, String signature) {
if (lastUpdateTime == null
Expand All @@ -96,6 +93,16 @@ public boolean verify(String serialNumber, byte[] message, String signature) {
return verifier.verify(serialNumber, message, signature);
}

@Override
public X509Certificate getValidCertificate() {
return verifier.getValidCertificate();
}

@Override
public X509Certificate getLatestCertificate() {
return verifier.getLatestCertificate();
}

protected void autoUpdateCert() throws IOException, GeneralSecurityException {
try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withCredentials(credentials)
Expand All @@ -122,9 +129,6 @@ protected void autoUpdateCert() throws IOException, GeneralSecurityException {
}
}

/**
* 反序列化证书并解密
*/
protected List<X509Certificate> deserializeToCerts(byte[] apiV3Key, String body)
throws GeneralSecurityException, IOException {
AesUtil aesUtil = new AesUtil(apiV3Key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

/**
Expand All @@ -26,6 +27,16 @@ public CertificatesVerifier(List<X509Certificate> list) {
}
}

public CertificatesVerifier(Map<BigInteger, X509Certificate> certificates) {
this.certificates.putAll(certificates);
}


public void updateCertificates(Map<BigInteger, X509Certificate> certificates) {
this.certificates.clear();
this.certificates.putAll(certificates);
}

protected boolean verify(X509Certificate certificate, byte[] message, String signature) {
try {
Signature sign = Signature.getInstance("SHA256withRSA");
Expand Down Expand Up @@ -61,4 +72,16 @@ public X509Certificate getValidCertificate() {
throw new NoSuchElementException("没有有效的微信支付平台证书");
}

@Override
public X509Certificate getLatestCertificate() {
X509Certificate latestCert = null;
for (X509Certificate x509Cert : certificates.values()) {
// 若latestCert为空或x509Cert的证书有效开始时间在latestCert之后,则更新latestCert
if (latestCert == null || x509Cert.getNotBefore().after(latestCert.getNotBefore())) {
latestCert = x509Cert;
}
}
return latestCert;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.wechat.pay.contrib.apache.httpclient.auth;

import com.wechat.pay.contrib.apache.httpclient.Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertManagerSingleton;
import java.security.cert.X509Certificate;
import java.util.concurrent.locks.ReentrantLock;

/**
* 在原有CertificatesVerifier基础上,增加定时更新证书功能(默认1小时)
*
* @author lianup
* @since 0.3.0
*/
public class ScheduledUpdateCertificatesVerifier implements Verifier {

protected static final int UPDATE_INTERVAL_MINUTE = 60;
private final ReentrantLock lock;
private final CertManagerSingleton certManagerSingleton;
private final CertificatesVerifier verifier;

public ScheduledUpdateCertificatesVerifier(Credentials credentials, byte[] apiv3Key) {
lock = new ReentrantLock();
certManagerSingleton = CertManagerSingleton.getInstance();
initCertManager(credentials, apiv3Key);
verifier = new CertificatesVerifier(certManagerSingleton.getCertificates());
}

public void initCertManager(Credentials credentials, byte[] apiv3Key) {
if (credentials == null || apiv3Key.length == 0) {
throw new IllegalArgumentException("credentials或apiv3Key为空");
}
certManagerSingleton.init(credentials, apiv3Key, UPDATE_INTERVAL_MINUTE);
}

@Override
public X509Certificate getLatestCertificate() {
return certManagerSingleton.getLatestCertificate();
}

@Override
public boolean verify(String serialNumber, byte[] message, String signature) {
if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) {
throw new IllegalArgumentException("serialNumber或message或signature为空");
}
if (lock.tryLock()) {
try {
verifier.updateCertificates(certManagerSingleton.getCertificates());
} finally {
lock.unlock();
}
}
return verifier.verify(serialNumber, message, signature);
}

/**
* 该方法已废弃,请勿使用
*
* @return null
*/
@Deprecated
@Override
public X509Certificate getValidCertificate() {
return null;
}


/**
* 停止定时更新,停止后无法再重新启动
*/
public void stopScheduledUpdate() {
certManagerSingleton.close();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ public interface Verifier {

boolean verify(String serialNumber, byte[] message, String signature);

/**
* 该方法已废弃,请使用getLatestCertificate代替
*
* @return 合法证书
*/
@Deprecated
X509Certificate getValidCertificate();

X509Certificate getLatestCertificate();

}
Loading

0 comments on commit 58d3de5

Please sign in to comment.