mirror of
				https://gitee.com/binary/weixin-java-tools.git
				synced 2025-10-31 10:38:42 +08:00 
			
		
		
		
	🆕 #3402 【微信支付】支持配置微信支付公钥
This commit is contained in:
		| @ -6,6 +6,17 @@ import com.github.binarywang.wxpay.util.ResourcesUtils; | |||||||
| import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder; | import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder; | ||||||
| import com.github.binarywang.wxpay.v3.auth.*; | import com.github.binarywang.wxpay.v3.auth.*; | ||||||
| import com.github.binarywang.wxpay.v3.util.PemUtils; | 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.Data; | ||||||
| import lombok.EqualsAndHashCode; | import lombok.EqualsAndHashCode; | ||||||
| import lombok.SneakyThrows; | import lombok.SneakyThrows; | ||||||
| @ -16,17 +27,6 @@ import org.apache.commons.lang3.StringUtils; | |||||||
| import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||||
| import org.apache.http.ssl.SSLContexts; | 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; |   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 秘钥值. |    * apiV3 秘钥值. | ||||||
|    */ |    */ | ||||||
| @ -241,7 +260,7 @@ public class WxPayConfig { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(), |     try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(), | ||||||
|       this.keyContent, "p12证书");) { |       this.keyContent, "p12证书")) { | ||||||
|       KeyStore keystore = KeyStore.getInstance("PKCS12"); |       KeyStore keystore = KeyStore.getInstance("PKCS12"); | ||||||
|       char[] partnerId2charArray = this.getMchId().toCharArray(); |       char[] partnerId2charArray = this.getMchId().toCharArray(); | ||||||
|       keystore.load(inputStream, partnerId2charArray); |       keystore.load(inputStream, partnerId2charArray); | ||||||
| @ -284,7 +303,6 @@ public class WxPayConfig { | |||||||
|           this.privateKeyContent, "privateKeyPath")) { |           this.privateKeyContent, "privateKeyPath")) { | ||||||
|           merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream); |           merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|       } |       } | ||||||
|       if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) { |       if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) { | ||||||
|         try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(), |         try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(), | ||||||
| @ -293,13 +311,28 @@ public class WxPayConfig { | |||||||
|         } |         } | ||||||
|         this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase(); |         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正向代理 |       //构造Http Proxy正向代理 | ||||||
|       WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy(); |       WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy(); | ||||||
|  |  | ||||||
|       AutoUpdateCertificatesVerifier certificatesVerifier = new AutoUpdateCertificatesVerifier( |       Verifier certificatesVerifier; | ||||||
|         new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)), |       if (publicKey == null) { | ||||||
|         this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(), this.getPayBaseUrl(), wxPayHttpProxy); |         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() |       WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create() | ||||||
|         .withMerchant(mchId, certSerialNo, merchantPrivateKey) |         .withMerchant(mchId, certSerialNo, merchantPrivateKey) | ||||||
| @ -422,7 +455,7 @@ public class WxPayConfig { | |||||||
|  |  | ||||||
|     // 分解p12证书文件 |     // 分解p12证书文件 | ||||||
|     try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(), |     try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(), | ||||||
|       this.keyContent, "p12证书");) { |       this.keyContent, "p12证书")) { | ||||||
|       KeyStore keyStore = KeyStore.getInstance("PKCS12"); |       KeyStore keyStore = KeyStore.getInstance("PKCS12"); | ||||||
|       keyStore.load(inputStream, key.toCharArray()); |       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; | package com.github.binarywang.wxpay.v3.util; | ||||||
|  |  | ||||||
| import me.chanjar.weixin.common.error.WxRuntimeException; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayOutputStream; | import java.io.ByteArrayOutputStream; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
| import java.security.KeyFactory; | import java.security.KeyFactory; | ||||||
| import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||||
| import java.security.PrivateKey; | import java.security.PrivateKey; | ||||||
|  | import java.security.PublicKey; | ||||||
| import java.security.cert.CertificateException; | import java.security.cert.CertificateException; | ||||||
| import java.security.cert.CertificateExpiredException; | import java.security.cert.CertificateExpiredException; | ||||||
| import java.security.cert.CertificateFactory; | import java.security.cert.CertificateFactory; | ||||||
| @ -15,7 +14,9 @@ import java.security.cert.CertificateNotYetValidException; | |||||||
| import java.security.cert.X509Certificate; | import java.security.cert.X509Certificate; | ||||||
| import java.security.spec.InvalidKeySpecException; | import java.security.spec.InvalidKeySpecException; | ||||||
| import java.security.spec.PKCS8EncodedKeySpec; | import java.security.spec.PKCS8EncodedKeySpec; | ||||||
|  | import java.security.spec.X509EncodedKeySpec; | ||||||
| import java.util.Base64; | import java.util.Base64; | ||||||
|  | import me.chanjar.weixin.common.error.WxRuntimeException; | ||||||
|  |  | ||||||
| public class PemUtils { | public class PemUtils { | ||||||
|  |  | ||||||
| @ -59,4 +60,28 @@ public class PemUtils { | |||||||
|       throw new WxRuntimeException("无效的证书", e); |       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> |   <certSerialNo>apiV3 证书序列号值</certSerialNo> | ||||||
|   <privateKeyPath>apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateKeyPath> |   <privateKeyPath>apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateKeyPath> | ||||||
|   <privateCertPath>apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateCertPath> |   <privateCertPath>apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateCertPath> | ||||||
|  |   <publicKeyPath>pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</publicKeyPath> | ||||||
|  |  | ||||||
|   <!-- other配置 --> |   <!-- other配置 --> | ||||||
|   <openid>某个openId</openid> |   <openid>某个openId</openid> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 栈烟
					栈烟