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发送请求。