8000 new: #3402 【微信支付】支持配置微信支付公钥 · binarywang/WxJava@46dab3a · GitHub
[go: up one dir, main page]

Skip to content

Commit 46dab3a

Browse files
new: #3402 【微信支付】支持配置微信支付公钥
1 parent c6a38ae commit 46dab3a

File tree

5 files changed

+267
-19
lines changed

5 files changed

+267
-19
lines changed

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@
66
import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder;
77
import com.github.binarywang.wxpay.v3.auth.*;
88
import com.github.binarywang.wxpay.v3.util.PemUtils;
9-
import lombok.Data;
10-
import lombok.EqualsAndHashCode;
11-
import lombok.SneakyThrows;
12-
import lombok.ToString;
13-
import lombok.extern.slf4j.Slf4j;
14-
import org.apache.commons.lang3.RegExUtils;
15-
import org.apache.commons.lang3.StringUtils;
16-
import org.apache.http.impl.client.CloseableHttpClient;
17-
import org.apache.http.ssl.SSLContexts;
18-
19-
import javax.net.ssl.SSLContext;
209
import java.io.*;
2110
import java.net.URL;
2211
import java.nio.charset.StandardCharsets;
2312
import java.security.KeyStore;
2413
import java.security.PrivateKey;
14+
import java.security.PublicKey;
2515
import java.security.cert.Certificate;
2616
import java.security.cert.X509Certificate;
2717
import java.util.Base64;
2818
import java.util.Optional;
19+
import javax.net.ssl.SSLContext;
20+
import lombok.Data;
21+
import lombok.EqualsAndHashCode;
22+
import lombok.SneakyThrows;
23+
import lombok.ToString;
24+
import lombok.extern.slf4j.Slf4j;
25+
import org.apache.commons.lang3.RegExUtils;
26+
import org.apache.commons.lang3.StringUtils;
27+
import org.apache.http.impl.client.CloseableHttpClient;
28+
import org.apache.http.ssl.SSLContexts;
2929

3030
/**
3131
* 微信支付配置
@@ -138,6 +138,25 @@ public class WxPayConfig {
138138
*/
139139
private byte[] privateCertContent;
140140

141+
/**
142+
* 公钥ID
143+
*/
144+
private String publicKeyId;
145+
146+
/**
147+
* pub_key.pem证书base64编码
148+
*/
149+
private String publicKeyString;
150+
151+
/**
152+
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
153+
*/
154+
private String publicKeyPath;
155+
156+
/**
157+
* pub_key.pem证书文件内容的字节数组.
158+
*/
159+
private byte[] publicKeyContent;
141160
/**
142161
* apiV3 秘钥值.
143162
*/
@@ -241,7 +260,7 @@ public SSLContext initSSLContext() throws WxPayException {
241260
}
242261

243262
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
244-
this.keyContent, "p12证书");) {
263+
this.keyContent, "p12证书")) {
245264
KeyStore keystore = KeyStore.getInstance("PKCS12");
246265
char[] partnerId2charArray = this.getMchId().toCharArray();
247266
keystore.load(inputStream, partnerId2charArray);
@@ -284,7 +303,6 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
284303
this.privateKeyContent, "privateKeyPath")) {
285304
merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream);
286305
}
287-
288306
}
289307
if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) {
290308
try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(),
@@ -293,13 +311,28 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
293311
}
294312
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
295313
}
314+
PublicKey publicKey = null;
315+
if (this.getPublicKeyString() != null || this.getPublicKeyPath() != null || this.publicKeyContent != null) {
316+
try (InputStream pubInputStream =
317+
this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(),
318+
this.publicKeyContent, "publicKeyPath")) {
319+
publicKey = PemUtils.loadPublicKey(pubInputStream);
320+
}
321+
}
296322

297323
//构造Http Proxy正向代理
298324
WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy();
299325

300-
AutoUpdateCertificatesVerifier certificatesVerifier = new AutoUpdateCertificatesVerifier(
301-
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
302-
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(), this.getPayBaseUrl(), wxPayHttpProxy);
326+
Verifier certificatesVerifier;
327+
if (publicKey == null) {
328+
certificatesVerifier =
329+
new AutoUpdateCertificatesVerifier(
330+
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
331+
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(),
332+
this.getPayBaseUrl(), wxPayHttpProxy);
333+
} else {
334+
certificatesVerifier = new PublicCertificateVerifier(publicKey, publicKeyId);
335+
}
303336

304337
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
305338
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
@@ -422,7 +455,7 @@ private Object[] p12ToPem() {
422455

423456
// 分解p12证书文件
424457
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
425-
this.keyContent, "p12证书");) {
458+
this.keyContent, "p12证书")) {
426459
KeyStore keyStore = KeyStore.getInstance("PKCS12");
427460
keyStore.load(inputStream, key.toCharArray());
428461

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.github.binarywang.wxpay.v3.auth;
2+
3+
import java.security.*;
4+
import java.security.cert.X509Certificate;
5+
import java.util.Base64;
6+
import me.chanjar.weixin.common.error.WxRuntimeException;
7+
8+
public class PublicCertificateVerifier implements Verifier{
9+
10+
private final PublicKey publicKey;
11+
12+
private final X509PublicCertificate publicCertificate;
13+
14+
public PublicCertificateVerifier(PublicKey publicKey, String publicId) {
15+
this.publicKey = publicKey;
16+
this.publicCertificate = new X509PublicCertificate(publicKey, publicId);
17+
}
18+
19+
@Override
20+
public boolean verify(String serialNumber, byte[] message, String signature) {
21+
try {
22+
Signature sign = Signature.getInstance("SHA256withRSA");
23+
sign.initVerify(publicKey);
24+
sign.update(message);
25+
return sign.verify(Base64.getDecoder().decode(signature));
26+
} catch (NoSuchAlgorithmException e) {
27+
throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e);
28+
} catch (SignatureException e) {
29+
throw new WxRuntimeException("签名验证过程发生了错误", e);
30+
} catch (InvalidKeyException e) {
31+
throw new WxRuntimeException("无效的证书", e);
32+
}
33+
}
34+
35+
@Override
36+
public X509Certificate getValidCertificate() {
37+
return this.publicCertificate;
38+
}
39+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package com.github.binarywang.wxpay.v3.auth;
2+
3+
import java.math.BigInteger;
4+
import java.security.*;
5+
import java.security.cert.*;
6+
import java.util.Collections;
7+
import java.util.Date;
8+
import java.util.Set;
9+
10+
public class X509PublicCertificate extends X509Certificate {
11+
12+
private final PublicKey publicKey;
13+
14+
private final String publicId;
15+
16+
public X509PublicCertificate(PublicKey publicKey, String publicId) {
17+
this.publicKey = publicKey;
18+
this.publicId = publicId;
19+
}
20+
21+
57AE @Override
22+
public PublicKey getPublicKey() {
23+
return this.publicKey;
24+
}
25+
26+
@Override
27+
public BigInteger getSerialNumber() {
28+
return new BigInteger(publicId.replace("PUB_KEY_ID_", ""), 16);
29+
}
30+
31+
@Override
32+
public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException {
33+
}
34+
35+
@Override
36+
public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException {
37+
38+
}
39+
40+
@Override
41+
public int getVersion() {
42+
return 0;
43+
}
44+
45+
@Override
46+
public Principal getIssuerDN() {
47+
return null;
48+
}
49+
50+
@Override
51+
public Principal getSubjectDN() {
52+
return null;
53+
}
54+
55+
@Override
56+
public Date getNotBefore() {
57+
return null;
58+
}
59+
60+
@Override
61+
public Date getNotAfter() {
62+
return null;
63+
}
64+
65+
@Override
66+
public byte[] getTBSCertificate() throws CertificateEncodingException {
67+
return new byte[0];
68+
}
69+
70+
@Override
71+
public byte[] getSignature() {
72+
return new byte[0];
73+
}
74+
75+
@Override
76+
public String getSigAlgName() {
77+
return "";
78+
}
79+
80+
@Override
81+
public String getSigAlgOID() {
82+
return "";
83+
}
84+
85+
@Override
86+
public byte[] getSigAlgParams() {
87+
return new byte[0];
88+
}
89+
90+
@Override
91+
public boolean[] getIssuerUniqueID() {
92+
return new boolean[0];
93+
}
94+
95+
@Override
96+
public boolean[] getSubjectUniqueID() {
97+
return new boolean[0];
98+
}
99+
100+
@Override
101+
public boolean[] getKeyUsage() {
102+
return new boolean[0];
103+
}
104+
105+
@Override
106+
public int getBasicConstraints() {
107+
return 0;
108+
}
109+
110+
@Override
111+
public byte[] getEncoded() throws CertificateEncodingException {
112+
return new byte[0];
113+
}
114+
115+
@Override
116+
public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
117+
118+
}
119+
120+
@Override
121+
public void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
122+
123+
}
124+
125+
@Override
126+
public String toString() {
127+
return "";
128+
}
129+
130+
131+
@Override
132+
public boolean hasUnsupportedCriticalExtension() {
133+
return false;
134+
}
135+
136+
@Override
137+
public Set<String> getCriticalExtensionOIDs() {
138+
return Collections.emptySet();
139+
}
140+
141+
@Override
142+
public Set<String> getNonCriticalExtensionOIDs() {
143+
return Collections.emptySet();
144+
}
145+
146+
@Override
147+
public byte[] getExtensionValue(String oid) {
148+
return new byte[0];
149+
}
150+
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
package com.github.binarywang.wxpay.v3.util;< 10000 /div>
22

3-
import me.chanjar.weixin.common.error.WxRuntimeException;
4-
53
import java.io.ByteArrayOutputStream;
64
import java.io.IOException;
75
import java.io.InputStream;
86
import java.security.KeyFactory;
97
import java.security.NoSuchAlgorithmException;
108
import java.security.PrivateKey;
9+
import java.security.PublicKey;
1110
import java.security.cert.CertificateException;
1211
import java.security.cert.CertificateExpiredException;
1312
import java.security.cert.CertificateFactory;
1413
import java.security.cert.CertificateNotYetValidException;
1514
import java.security.cert.X509Certificate;
1615
import java.security.spec.InvalidKeySpecException;
1716
import java.security.spec.PKCS8EncodedKeySpec;
17+
import java.security.spec.X509EncodedKeySpec;
1818
import java.util.Base64;
19+
import me.chanjar.weixin.common.error.WxRuntimeException;
1920

2021
public class PemUtils {
2122

@@ -59,4 +60,28 @@ public static X509Certificate loadCertificate(InputStream inputStream) {
5960
throw new WxRuntimeException("无效的证书", e);
6061
}
6162
}
63+
64+
public static PublicKey loadPublicKey(InputStream inputStream){
65+
try {
66+
ByteArrayOutputStream array = new ByteArrayOutputStream();
67+
byte[] buffer = new byte[1024];
68+
int length;
69+
while ((length = inputStream.read(buffer)) != -1) {
70+
array.write(buffer, 0, length);
71+
}
72+
73+
String publicKey = array.toString("utf-8")
74+
.replace("-----BEGIN PUBLIC KEY-----", "")
75+
.replace("-----END PUBLIC KEY-----", "")
76+
.replaceAll("\\s+", "");
77+
return KeyFactory.getInstance("RSA")
78+
.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
79+
} catch (NoSuchAlgorithmException e) {
80+
throw new WxRuntimeException("当前Java环境不支持RSA", e);
81+
} catch (InvalidKeySpecException e) {
82+
throw new WxRuntimeException("无效的密钥格式");
83+
} catch (IOException e) {
84+
throw new WxRuntimeException("无效的密钥");
85+
}
86+
}
6287
}

weixin-java-pay/src/test/resources/test-config.sample.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<certSerialNo>apiV3 证书序列号值</certSerialNo>
1919
<privateKeyPath>apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateKeyPath>
2020
<privateCertPath>apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateCertPath>
21+
<publicKeyPath>pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</publicKeyPath>
2122

2223
<!-- other配置 -->
2324
<openid>某个openId</openid>

0 commit comments

Comments
 (0)
0