package com.dsh.activity.util.wx;
|
|
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
|
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
|
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
|
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
|
import lombok.SneakyThrows;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
import javax.servlet.http.HttpServletRequest;
|
import java.io.*;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.Files;
|
import java.nio.file.Paths;
|
import java.security.*;
|
import java.security.cert.Certificate;
|
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateFactory;
|
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 java.util.HashMap;
|
import java.util.Map;
|
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.stream.Collectors;
|
import java.util.stream.Stream;
|
|
/**
|
* 微信支付V3 API 签名验证工具类
|
*/
|
public class WXPaySignatureCertificateUtil {
|
|
// 保存微信平台证书
|
private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>();
|
// 缓存公钥,避免频繁IO操作
|
private static PublicKey cachedPublicKey = null;
|
// 缓存私钥,避免频繁IO操作
|
private static PrivateKey cachedPrivateKey = null;
|
|
/**
|
* 获取HTTP客户端,用于微信支付API请求
|
*/
|
public static CloseableHttpClient getWechatPayClient() throws IOException {
|
PrivateKey merchantPrivateKey = getPrivateKey();
|
// 构建支付客户端
|
return WechatPayHttpClientBuilder.create()
|
.withMerchant(WxV3PayConfig.Mch_ID, WxV3PayConfig.mchSerialNo, merchantPrivateKey)
|
.withValidator(new WechatPay2Validator(getVerifier()))
|
.build();
|
}
|
|
/**
|
* 获取自动更新的证书验证器
|
*/
|
public static AutoUpdateCertificatesVerifier getVerifier() {
|
String mchSerialNo = WxV3PayConfig.mchSerialNo;
|
AutoUpdateCertificatesVerifier verifier = verifierMap.get(mchSerialNo);
|
|
if (verifier == null) {
|
synchronized (WXPaySignatureCertificateUtil.class) {
|
verifier = verifierMap.get(mchSerialNo);
|
if (verifier == null) {
|
try {
|
PrivateKey privateKey = getPrivateKey();
|
PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, privateKey);
|
WechatPay2Credentials credentials = new WechatPay2Credentials(WxV3PayConfig.Mch_ID, signer);
|
verifier = new AutoUpdateCertificatesVerifier(
|
credentials, WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
|
verifierMap.put(mchSerialNo, verifier);
|
} catch (Exception e) {
|
throw new RuntimeException("初始化证书验证器失败", e);
|
}
|
}
|
}
|
}
|
|
return verifier;
|
}
|
|
/**
|
* 使用商户私钥对数据进行签名
|
*
|
* @param message 待签名数据
|
* @return Base64编码的签名结果
|
*/
|
public static String sign(String message) {
|
try {
|
PrivateKey privateKey = getPrivateKey();
|
Signature signature = Signature.getInstance("SHA256withRSA");
|
signature.initSign(privateKey);
|
signature.update(message.getBytes(StandardCharsets.UTF_8));
|
return Base64.getEncoder().encodeToString(signature.sign());
|
} catch (Exception e) {
|
throw new RuntimeException("签名失败", e);
|
}
|
}
|
|
/**
|
* 使用微信支付平台公钥验证签名
|
*
|
* @param message 原始消息
|
* @param signature Base64编码的签名
|
* @return 验证结果
|
*/
|
public static boolean verifySign(String message, String signature) {
|
try {
|
PublicKey publicKey = getWechatPublicKey();
|
Signature sig = Signature.getInstance("SHA256withRSA");
|
sig.initVerify(publicKey);
|
sig.update(message.getBytes(StandardCharsets.UTF_8));
|
return sig.verify(Base64.getDecoder().decode(signature));
|
} catch (Exception e) {
|
throw new RuntimeException("验签失败", e);
|
}
|
}
|
|
/**
|
* 验证微信支付通知消息的签名
|
*
|
* @param request HTTP请求
|
* @param body 请求体内容
|
* @return 验证结果
|
*/
|
public static boolean verifyNotify(HttpServletRequest request, String body) {
|
try {
|
// 获取必要的HTTP头信息
|
String timestamp = request.getHeader("Wechatpay-Timestamp");
|
String nonce = request.getHeader("Wechatpay-Nonce");
|
String serialNo = request.getHeader("Wechatpay-Serial");
|
String signature = request.getHeader("Wechatpay-Signature");
|
|
// 拼接验签字符串
|
String message = Stream.of(timestamp, nonce, body)
|
.collect(Collectors.joining("\n", "", "\n"));
|
|
// 使用自动更新的证书验证器进行验证
|
return getVerifier().verify(serialNo, message.getBytes(StandardCharsets.UTF_8), signature);
|
} catch (Exception e) {
|
throw new RuntimeException("验证微信支付通知签名失败", e);
|
}
|
}
|
|
/**
|
* APP支付签名
|
*/
|
public static String appPaySign(String timestamp, String nonceStr, String prepayId) {
|
String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, prepayId)
|
.collect(Collectors.joining("\n", "", "\n"));
|
return sign(message);
|
}
|
|
/**
|
* JSAPI支付签名
|
*/
|
public static String jsApiPaySign(String timestamp, String nonceStr, String prepayId) {
|
String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, "prepay_id="+prepayId)
|
.collect(Collectors.joining("\n", "", "\n"));
|
return sign(message);
|
}
|
|
/**
|
* 构建App支付参数
|
*/
|
public static Map<String, String> buildAppPayParams(String prepayId) {
|
try {
|
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
String nonceStr = generateNonceStr();
|
String sign = appPaySign(timestamp, nonceStr, prepayId);
|
Map<String, String> params = new HashMap<>();
|
params.put("appid", WxV3PayConfig.APP_ID);
|
params.put("partnerid", WxV3PayConfig.Mch_ID);
|
params.put("prepayid", prepayId);
|
params.put("package", "Sign=WXPay");
|
params.put("noncestr", nonceStr);
|
params.put("timestamp", timestamp);
|
params.put("sign", sign);
|
|
return params;
|
} catch (Exception e) {
|
throw new RuntimeException("构建App支付参数失败", e);
|
}
|
}
|
|
/**
|
* 构建JSAPI支付参数
|
*/
|
public static Map<String, String> buildJsApiPayParams(String prepayId) {
|
try {
|
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
String nonceStr = generateNonceStr();
|
String sign = jsApiPaySign(timestamp, nonceStr, prepayId);
|
|
Map<String, String> params = new HashMap<>();
|
params.put("appId", WxV3PayConfig.APP_ID);
|
params.put("timeStamp", timestamp);
|
params.put("nonceStr", nonceStr);
|
params.put("package", "prepay_id=" + prepayId);
|
params.put("signType", "RSA");
|
params.put("paySign", sign);
|
|
return params;
|
} catch (Exception e) {
|
throw new RuntimeException("构建JSAPI支付参数失败", e);
|
}
|
}
|
|
/**
|
* 生成随机字符串
|
*/
|
private static String generateNonceStr() {
|
return java.util.UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
|
}
|
|
/**
|
* 获取商户私钥
|
*/
|
public static PrivateKey getPrivateKey() {
|
if (cachedPrivateKey != null) {
|
return cachedPrivateKey;
|
}
|
|
try {
|
String filePath = "D:\\玩湃v3参数\\apiclient_key.pem";
|
String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
|
|
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
|
.replace("-----END PRIVATE KEY-----", "")
|
.replaceAll("\\s+", "");
|
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
cachedPrivateKey = kf.generatePrivate(
|
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
|
return cachedPrivateKey;
|
} catch (IOException e) {
|
throw new RuntimeException("读取私钥文件失败", e);
|
} catch (NoSuchAlgorithmException e) {
|
throw new RuntimeException("当前Java环境不支持RSA", e);
|
} catch (InvalidKeySpecException e) {
|
throw new RuntimeException("无效的密钥格式", e);
|
}
|
}
|
|
/**
|
* 获取微信支付平台公钥
|
*/
|
public static PublicKey getWechatPublicKey() {
|
if (cachedPublicKey != null) {
|
return cachedPublicKey;
|
}
|
|
try {
|
// 使用验证器获取最新的证书
|
X509Certificate certificate = getVerifier().getValidCertificate();
|
cachedPublicKey = certificate.getPublicKey();
|
return cachedPublicKey;
|
} catch (Exception e) {
|
throw new RuntimeException("获取微信平台公钥失败", e);
|
}
|
}
|
|
/**
|
* 从PEM格式文件加载公钥
|
*/
|
public static PublicKey loadPublicKeyFromFile(String filePath) {
|
try {
|
String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
|
|
String publicKeyPEM = content
|
.replace("-----BEGIN PUBLIC KEY-----", "")
|
.replace("-----END PUBLIC KEY-----", "")
|
.replaceAll("\\s+", "");
|
|
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
|
} catch (Exception e) {
|
throw new RuntimeException("加载公钥文件失败", e);
|
}
|
}
|
|
/**
|
* 从证书文件加载公钥
|
*/
|
public static PublicKey loadPublicKeyFromCert(String filePath) {
|
try (FileInputStream inputStream = new FileInputStream(filePath)) {
|
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream);
|
return cert.getPublicKey();
|
} catch (Exception e) {
|
throw new RuntimeException("加载证书文件失败", e);
|
}
|
}
|
}
|