API v3版微信支付请求签名
- 获取证书私钥
- 构造签名串
- 生成签名
- 设置请求头
请求签名是微信用来验证请求的合法性的,签名是放在请求头中的编码串。
获取证书私钥
商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件apiclient_key.pem 中。可以将私钥串儿写到项目的配置文件中(注意中间不能有换行),也可以读取私钥文件获得。
如何申请商户证书在上一篇文章中。
/**
* 读取私钥文件
* @return 私钥对象
*/
public static PrivateKey getWXPayPrivateKey() throws IOException{
String content = new String(WXPayUtil.getConfigProperties("WX_SIGN_PRIVATEKEY"));
try {
// replace: 将匹配到的字符替换
// \s+: 正则匹配,\s代表空白字符(包括空格制表符等),+ 代表任意多,将所有匹配到的空白字符替换为空
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密码格式", e);
}
}
构造签名串
签名串格式为五行,每一行为一个参数,行尾以 \n结束。报文主体可能为空,此时依然有\n。
注:若报文主体为空则可能是null\n,需要注意直接空字符串即可,不要null。
HTTP请求方法\n
URL\n
请求时间戳\n
请求随机串\n
请求报文主体\n
String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ ((body == null)?"\n":request.getBodyPost() + "\n"));
}
生成签名
使用商户私钥对上述待签名串儿进行SHA256withRSA签名,并将签名值进行Base64编码。
/**
* 签名
* @param message 签名原文
* @param privateKey 私钥
* @param certificateSerialNumber 证书序列号
* @return 签名值
*/
public static String sign(byte[] message, PrivateKey privateKey) {
try {
Signature sign = Signature.getInstance(SIGN_INSTANCE_SHA256WITHTSA);
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (SignatureException e) {
throw new RuntimeException("签名计算失败", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("无效的私钥", e);
}
}
设置请求头
将生成的签名与其他信息一起按照格式放入请求头Authorization中,请求头格式为
Authorization: WECHATPAY2-SHA256-RSA2048 {Token}
/**
* 拼装HTTP请求头信息,传递签名(根据平台规则生成请求头参数Authorization)
*
* @param request 请求
* @param body 请求体
* @return 返回信息
*/
public static String getToken(WXRequestParam request) throws IOException{
String strNonce = generateNonceStr(); // 请求随机数
long timestemp = generateTimestamp(); // 时间戳
String message = buildMessage(strNonce, timestemp, request);
log.info("WXPayUtil-getToken拼装HTTP请求头信息 message = " + message);
PrivateKey privateKey = WXPayUtil.getWXPayPrivateKey();
String signature = WXPayUtil.sign(message.getBytes("utf-8"), privateKey);
return "mchid=\"" + WXPayUtil.getConfigProperties("WX_MERCHANTID") + "\","
+ "nonce_str=\"" + strNonce + "\","
+ "timestamp=\"" + timestemp + "\","
+ "serial_no=\"" + WXPayUtil.getConfigProperties("WX_SERIAL_NO") + "\","
+ "signature=\"" + signature + "\"";
}
完成之后即可调用API发送请求。