Skip to content

Commit 58d3de5

Browse files
Merge pull request #88 from lianup/master
增加平台证书管理器
2 parents bd5f22b + 4203017 commit 58d3de5

File tree

14 files changed

+530
-36
lines changed

14 files changed

+530
-36
lines changed

README.md

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88

99
## 项目状态
1010

11-
当前版本`0.2.3`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
11+
当前版本`0.3.0`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
12+
13+
## 升级指引
14+
15+
版本`0.3.0`提供了更可靠的[定时更新平台证书功能](#定时更新平台证书功能)。若你使用了自动更新证书功能,推荐升级并使用新的`ScheduledUpdateCertificatesVerifier`替换`AutoUpdateCertificatesVerifier`
16+
17+
注:`Verifier`接口新增了`getLatestCertificate()`方法。若你通过实现`Verifier`接口自定义了验签器,升级后需实现该方法。
1218

1319
## 环境要求
1420

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

2531
```groovy
26-
implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.2.3'
32+
implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.3.0'
2733
```
2834

2935
### Maven
@@ -33,7 +39,7 @@ implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.2.3'
3339
<dependency>
3440
<groupId>com.github.wechatpay-apiv3</groupId>
3541
<artifactId>wechatpay-apache-httpclient</artifactId>
36-
<version>0.2.3</version>
42+
<version>0.3.0</version>
3743
</dependency>
3844
```
3945

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

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

6369
// 后面跟使用Apache HttpClient一样
64-
HttpResponse response = httpClient.execute(...);
70+
ClosableHttpResponse response = httpClient.execute(...);
6571
```
6672

6773
参数说明:
6874

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

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

@@ -176,36 +182,35 @@ WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
176182
.withWechatPay(wechatpayCertificates);
177183
```
178184

179-
## 自动更新证书功能
185+
## 定时更新平台证书功能
180186

181-
版本`>=0.1.5`可使用 AutoUpdateCertificatesVerifier 类替代默认的验签器。它会在构造时自动下载商户对应的[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu),并每隔一段时间(默认为1个小时)更新证书。
182-
183-
参数说明:`apiV3Key`是String格式的API v3密钥。
187+
版本>=`0.3.0`可使用 ScheduledUpdateCertificatesVerifier 类替代默认的验签器。它会定时下载和更新商户对应的[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu) (默认为1小时)。
184188

185189
示例代码:
186190

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

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

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

201205
// 后面跟使用Apache HttpClient一样
202-
HttpResponse response = httpClient.execute(...);
206+
CloseableHttpResponse response = httpClient.execute(...);
203207
```
208+
204209
### 风险
205210

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

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

210215
## 敏感信息加解密
211216

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group 'com.github.wechatpay-apiv3'
8-
version '0.2.3'
8+
version '0.3.0'
99

1010
sourceCompatibility = 1.8
1111
targetCompatibility = 1.8

src/main/java/com/wechat/pay/contrib/apache/httpclient/WechatPayHttpClientBuilder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public WechatPayHttpClientBuilder withWechatPay(List<X509Certificate> certificat
5353

5454
/**
5555
* Please use {@link #withWechatPay(List)} instead
56+
*
57+
* @param certificates 平台证书list
58+
* @return 具有验证器的builder
5659
*/
5760
@SuppressWarnings("SpellCheckingInspection")
5861
@Deprecated

src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/AutoUpdateCertificatesVerifier.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@
3232

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

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

75-
@Override
76-
public X509Certificate getValidCertificate() {
77-
return verifier.getValidCertificate();
78-
}
79-
8077
@Override
8178
public boolean verify(String serialNumber, byte[] message, String signature) {
8279
if (lastUpdateTime == null
@@ -96,6 +93,16 @@ public boolean verify(String serialNumber, byte[] message, String signature) {
9693
return verifier.verify(serialNumber, message, signature);
9794
}
9895

96+
@Override
97+
public X509Certificate getValidCertificate() {
98+
return verifier.getValidCertificate();
99+
}
100+
101+
@Override
102+
public X509Certificate getLatestCertificate() {
103+
return verifier.getLatestCertificate();
104+
}
105+
99106
protected void autoUpdateCert() throws IOException, GeneralSecurityException {
100107
try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
101108
.withCredentials(credentials)
@@ -122,9 +129,6 @@ protected void autoUpdateCert() throws IOException, GeneralSecurityException {
122129
}
123130
}
124131

125-
/**
126-
* 反序列化证书并解密
127-
*/
128132
protected List<X509Certificate> deserializeToCerts(byte[] apiV3Key, String body)
129133
throws GeneralSecurityException, IOException {
130134
AesUtil aesUtil = new AesUtil(apiV3Key);

src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/CertificatesVerifier.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Base64;
1212
import java.util.HashMap;
1313
import java.util.List;
14+
import java.util.Map;
1415
import java.util.NoSuchElementException;
1516

1617
/**
@@ -26,6 +27,16 @@ public CertificatesVerifier(List<X509Certificate> list) {
2627
}
2728
}
2829

30+
public CertificatesVerifier(Map<BigInteger, X509Certificate> certificates) {
31+
this.certificates.putAll(certificates);
32+
}
33+
34+
35+
public void updateCertificates(Map<BigInteger, X509Certificate> certificates) {
36+
this.certificates.clear();
37+
this.certificates.putAll(certificates);
38+
}
39+
2940
protected boolean verify(X509Certificate certificate, byte[] message, String signature) {
3041
try {
3142
Signature sign = Signature.getInstance("SHA256withRSA");
@@ -61,4 +72,16 @@ public X509Certificate getValidCertificate() {
6172
throw new NoSuchElementException("没有有效的微信支付平台证书");
6273
}
6374

75+
@Override
76+
public X509Certificate getLatestCertificate() {
77+
X509Certificate latestCert = null;
78+
for (X509Certificate x509Cert : certificates.values()) {
79+
// 若latestCert为空或x509Cert的证书有效开始时间在latestCert之后,则更新latestCert
80+
if (latestCert == null || x509Cert.getNotBefore().after(latestCert.getNotBefore())) {
81+
latestCert = x509Cert;
82+
}
83+
}
84+
return latestCert;
85+
}
6486
}
87+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.wechat.pay.contrib.apache.httpclient.auth;
2+
3+
import com.wechat.pay.contrib.apache.httpclient.Credentials;
4+
import com.wechat.pay.contrib.apache.httpclient.cert.CertManagerSingleton;
5+
import java.security.cert.X509Certificate;
6+
import java.util.concurrent.locks.ReentrantLock;
7+
8+
/**
9+
* 在原有CertificatesVerifier基础上,增加定时更新证书功能(默认1小时)
10+
*
11+
* @author lianup
12+
* @since 0.3.0
13+
*/
14+
public class ScheduledUpdateCertificatesVerifier implements Verifier {
15+
16+
protected static final int UPDATE_INTERVAL_MINUTE = 60;
17+
private final ReentrantLock lock;
18+
private final CertManagerSingleton certManagerSingleton;
19+
private final CertificatesVerifier verifier;
20+
21+
public ScheduledUpdateCertificatesVerifier(Credentials credentials, byte[] apiv3Key) {
22+
lock = new ReentrantLock();
23+
certManagerSingleton = CertManagerSingleton.getInstance();
24+
initCertManager(credentials, apiv3Key);
25+
verifier = new CertificatesVerifier(certManagerSingleton.getCertificates());
26+
}
27+
28+
public void initCertManager(Credentials credentials, byte[] apiv3Key) {
29+
if (credentials == null || apiv3Key.length == 0) {
30+
throw new IllegalArgumentException("credentials或apiv3Key为空");
31+
}
32+
certManagerSingleton.init(credentials, apiv3Key, UPDATE_INTERVAL_MINUTE);
33+
}
34+
35+
@Override
36+
public X509Certificate getLatestCertificate() {
37+
return certManagerSingleton.getLatestCertificate();
38+
}
39+
40+
@Override
41+
public boolean verify(String serialNumber, byte[] message, String signature) {
42+
if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) {
43+
throw new IllegalArgumentException("serialNumber或message或signature为空");
44+
}
45+
if (lock.tryLock()) {
46+
try {
47+
verifier.updateCertificates(certManagerSingleton.getCertificates());
48+
} finally {
49+
lock.unlock();
50+
}
51+
}
52+
return verifier.verify(serialNumber, message, signature);
53+
}
54+
55+
/**
56+
* 该方法已废弃,请勿使用
57+
*
58+
* @return null
59+
*/
60+
@Deprecated
61+
@Override
62+
public X509Certificate getValidCertificate() {
63+
return null;
64+
}
65+
66+
67+
/**
68+
* 停止定时更新,停止后无法再重新启动
69+
*/
70+
public void stopScheduledUpdate() {
71+
certManagerSingleton.close();
72+
}
73+
74+
}

src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/Verifier.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ public interface Verifier {
99

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

12+
/**
13+
* 该方法已废弃,请使用getLatestCertificate代替
14+
*
15+
* @return 合法证书
16+
*/
17+
@Deprecated
1218
X509Certificate getValidCertificate();
1319

20+
X509Certificate getLatestCertificate();
21+
1422
}

0 commit comments

Comments
 (0)