Skip to content
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
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