mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2025-04-05 08:37:32 +08:00
🆕 #3402 【微信支付】支持配置微信支付公钥
This commit is contained in:
parent
c6a38ae7dd
commit
524ff80522
@ -6,6 +6,17 @@ import com.github.binarywang.wxpay.util.ResourcesUtils;
|
||||
import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder;
|
||||
import com.github.binarywang.wxpay.v3.auth.*;
|
||||
import com.github.binarywang.wxpay.v3.util.PemUtils;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.SneakyThrows;
|
||||
@ -16,17 +27,6 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.ssl.SSLContexts;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 微信支付配置
|
||||
*
|
||||
@ -138,6 +138,25 @@ public class WxPayConfig {
|
||||
*/
|
||||
private byte[] privateCertContent;
|
||||
|
||||
/**
|
||||
* 公钥ID
|
||||
*/
|
||||
private String publicKeyId;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书base64编码
|
||||
*/
|
||||
private String publicKeyString;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String publicKeyPath;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书文件内容的字节数组.
|
||||
*/
|
||||
private byte[] publicKeyContent;
|
||||
/**
|
||||
* apiV3 秘钥值.
|
||||
*/
|
||||
@ -241,7 +260,7 @@ public class WxPayConfig {
|
||||
}
|
||||
|
||||
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
|
||||
this.keyContent, "p12证书");) {
|
||||
this.keyContent, "p12证书")) {
|
||||
KeyStore keystore = KeyStore.getInstance("PKCS12");
|
||||
char[] partnerId2charArray = this.getMchId().toCharArray();
|
||||
keystore.load(inputStream, partnerId2charArray);
|
||||
@ -284,7 +303,6 @@ public class WxPayConfig {
|
||||
this.privateKeyContent, "privateKeyPath")) {
|
||||
merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream);
|
||||
}
|
||||
|
||||
}
|
||||
if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) {
|
||||
try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(),
|
||||
@ -293,13 +311,28 @@ public class WxPayConfig {
|
||||
}
|
||||
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
|
||||
}
|
||||
PublicKey publicKey = null;
|
||||
if (this.getPublicKeyString() != null || this.getPublicKeyPath() != null || this.publicKeyContent != null) {
|
||||
try (InputStream pubInputStream =
|
||||
this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(),
|
||||
this.publicKeyContent, "publicKeyPath")) {
|
||||
publicKey = PemUtils.loadPublicKey(pubInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
//构造Http Proxy正向代理
|
||||
WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy();
|
||||
|
||||
AutoUpdateCertificatesVerifier certificatesVerifier = new AutoUpdateCertificatesVerifier(
|
||||
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
|
||||
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(), this.getPayBaseUrl(), wxPayHttpProxy);
|
||||
Verifier certificatesVerifier;
|
||||
if (publicKey == null) {
|
||||
certificatesVerifier =
|
||||
new AutoUpdateCertificatesVerifier(
|
||||
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
|
||||
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(),
|
||||
this.getPayBaseUrl(), wxPayHttpProxy);
|
||||
} else {
|
||||
certificatesVerifier = new PublicCertificateVerifier(publicKey, publicKeyId);
|
||||
}
|
||||
|
||||
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
|
||||
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
|
||||
@ -422,7 +455,7 @@ public class WxPayConfig {
|
||||
|
||||
// 分解p12证书文件
|
||||
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
|
||||
this.keyContent, "p12证书");) {
|
||||
this.keyContent, "p12证书")) {
|
||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||
keyStore.load(inputStream, key.toCharArray());
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
package com.github.binarywang.wxpay.v3.auth;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
|
||||
public class PublicCertificateVerifier implements Verifier{
|
||||
|
||||
private final PublicKey publicKey;
|
||||
|
||||
private final X509PublicCertificate publicCertificate;
|
||||
|
||||
public PublicCertificateVerifier(PublicKey publicKey, String publicId) {
|
||||
this.publicKey = publicKey;
|
||||
this.publicCertificate = new X509PublicCertificate(publicKey, publicId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String serialNumber, byte[] message, String signature) {
|
||||
try {
|
||||
Signature sign = Signature.getInstance("SHA256withRSA");
|
||||
sign.initVerify(publicKey);
|
||||
sign.update(message);
|
||||
return sign.verify(Base64.getDecoder().decode(signature));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e);
|
||||
} catch (SignatureException e) {
|
||||
throw new WxRuntimeException("签名验证过程发生了错误", e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new WxRuntimeException("无效的证书", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate getValidCertificate() {
|
||||
return this.publicCertificate;
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package com.github.binarywang.wxpay.v3.auth;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.cert.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
public class X509PublicCertificate extends X509Certificate {
|
||||
|
||||
private final PublicKey publicKey;
|
||||
|
||||
private final String publicId;
|
||||
|
||||
public X509PublicCertificate(PublicKey publicKey, String publicId) {
|
||||
this.publicKey = publicKey;
|
||||
this.publicId = publicId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey() {
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getSerialNumber() {
|
||||
return new BigInteger(publicId.replace("PUB_KEY_ID_", ""), 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getIssuerDN() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getSubjectDN() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotBefore() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotAfter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getTBSCertificate() throws CertificateEncodingException {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgOID() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSigAlgParams() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getIssuerUniqueID() {
|
||||
return new boolean[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getSubjectUniqueID() {
|
||||
return new boolean[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getKeyUsage() {
|
||||
return new boolean[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBasicConstraints() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() throws CertificateEncodingException {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasUnsupportedCriticalExtension() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getCriticalExtensionOIDs() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNonCriticalExtensionOIDs() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getExtensionValue(String oid) {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
package com.github.binarywang.wxpay.v3.util;
|
||||
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
@ -15,7 +14,9 @@ import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
|
||||
public class PemUtils {
|
||||
|
||||
@ -59,4 +60,28 @@ public class PemUtils {
|
||||
throw new WxRuntimeException("无效的证书", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PublicKey loadPublicKey(InputStream inputStream){
|
||||
try {
|
||||
ByteArrayOutputStream array = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
while ((length = inputStream.read(buffer)) != -1) {
|
||||
array.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
String publicKey = array.toString("utf-8")
|
||||
.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.replace("-----END PUBLIC KEY-----", "")
|
||||
.replaceAll("\\s+", "");
|
||||
return KeyFactory.getInstance("RSA")
|
||||
.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new WxRuntimeException("当前Java环境不支持RSA", e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new WxRuntimeException("无效的密钥格式");
|
||||
} catch (IOException e) {
|
||||
throw new WxRuntimeException("无效的密钥");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
<certSerialNo>apiV3 证书序列号值</certSerialNo>
|
||||
<privateKeyPath>apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateKeyPath>
|
||||
<privateCertPath>apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateCertPath>
|
||||
<publicKeyPath>pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</publicKeyPath>
|
||||
|
||||
<!-- other配置 -->
|
||||
<openid>某个openId</openid>
|
||||
|
Loading…
Reference in New Issue
Block a user