微信加解密流程,证书作用讲解,官方SDK使用教程

微信支付接口常用参数及证书区分

1. 私钥和证书

1.1 商户API证书

1.1.1 功能介绍

API证书,是指由商户申请的,用来证实商户身份的证书。API证书由证书授权机构Certificate Authority(简称CA)颁发。证书中包含商户的商户号、公司名称、公钥等信息。

1.1.2 作用

签名生成
用来对请求url和body啥的签名,就是加密,微信收到请求,用证书公钥解密

商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件apiclient_key.pem 中

加密用的就是,apiclient_key.pem文件,生成证书文件时,另外两个没用到

使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值

1.2 微信支付平台证书

1.2.1 功能介绍

微信支付平台证书是指由微信支付 负责申请的,包含微信支付平台标识、公钥信息的证书。商户可以使用平台证书中的公钥进行验签。

1.2.2 作用

  1. 如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名,这时候就需要微信支付平台证书(给的是公钥)进行验签,保证是微信端的
  2. 回调信息和所有请求回来的信息都需要验签就需要,微信支付平台证书,他们那边用这个私钥对报文可能是body啥的加签,我们用公钥验签,保证安全
  3. 敏感信息加解密:上传某些重要信息时也需要用到这个微信支付平台证书公钥加密

1.2.3获取

获取平台证书
需要请求接口获取

1.2.4时效性

由于证书存在有效期的限制,微信支付会不定期地更换平台证书以确保交易安全。
估计12小时有效,所以要定期请求更新

1.3 apiv3密钥

为了保证安全性,微信支付在 回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。API v3密钥是加密时使用的对称密钥。
回调的信息用AES解密(密钥为APIV3值),得到的信息就是回调的明文内容

1.4 常见问题

第一次下载证书
对于微信支付平台的应答,需要使用平台证书来进行验签;但平台证书只能通过 获取平台证书接口 下载,所以当第一次去获取证书时,会出现个“死循环”。

为解决这个“死循环”,可以临时跳过验签,来获得证书。也就是说可以不提供微信支付证书参数(-c 参数)来下载,在下载得到证书后,工具会使用证书对报文的签名进行验证,如果通过则说明证书正确。

如何保证证书正确
工具已经从以下方面去保证了:

HTTPS:证书下载请求使用了 HTTPS
AES 加密:微信支付对证书信息进行了 AES-256-GCM 加密,所以工具得到应答后,会使用对称密钥来解密证书(这里需要用户传入对称密钥,出于对对称密钥安全的考虑,后续版本将可直接保存未解密的证书,由用户进行解密)
报文验签:微信支付会在应答的 HTTP 头包含签名,工具会通过解密得到的证书,来验证报文的签名,以此确认证书正确

1.5 官方SDK使用

wechatpay-apache-httpclient

实现了请求签名的生成和应答签名的验证,下载证书做了封装,没其他功能

httpClient记得设置超时时间,不然是默认的无超时,到时候接口爆炸就GG

在封装一个通用的工具类,获取HttpClient,实例化一次就够了,但是SDK并没有提供是否已经实例化过,所以只能取巧,从证书管理器中获取verifier是否异常来判断是否有实例化过
警告:这种写法有bug,当apiv3或者其他实例化HttpClient的参数改变时,需要重启应用才能生效,当然可以搞成配置修改时,重新实例化HttpClient


@Component
public class WxApiV3Utils {
    
    
	public static final String REDIS_KEY_RCHG_PARTNER_PAY_MRCH_CFG = "rchg_partner_pay_mrch_cfg";
	private static final Logger logger = LoggerFactory.getLogger(WxApiV3Utils.class);
	private static final Map<String, CloseableHttpClient> MERCHANT_ID_HTTP_CLIENT_MAP = new ConcurrentHashMap<>();

	/**
	 * 证书管理器实例
	 */
	private final CertificatesManager certificatesManager = CertificatesManager.getInstance();

	@Autowired
	private RedisUtils redisUtils;

	public CloseableHttpClient getHttpClient(String merchantId) throws Exception {
    
    
		// 获取ApiV3配置
		WxPayMrchCfg wxPayMrchCfg = getApiV3Cfg(merchantId);
		String merchantSerialNumber = wxPayMrchCfg.getCertSerialNo();
		String apiV3Key = wxPayMrchCfg.getPartnerKey();
		String certPrivateKeyPath = wxPayMrchCfg.getCertPrivateKeyPath();

		// 加载商户私钥
		PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(certPrivateKeyPath));

		// 获取验签器
		Verifier verifier = getVerifier(merchantId, merchantSerialNumber, apiV3Key, merchantPrivateKey);

		Optional<CloseableHttpClient> optional = getHttpClient(merchantId, merchantSerialNumber, merchantPrivateKey,
				verifier);
		if (!optional.isPresent()) {
    
    
			throw new RuntimeException("httpClient为空");
		}
		return optional.get();
	}

	private WxPayMrchCfg getApiV3Cfg(String merchantId) {
    
    
		String merchantIdCfg = redisUtils.getHashValue(REDIS_KEY_RCHG_PARTNER_PAY_MRCH_CFG, merchantId);
		if (StrUtil.isBlank(merchantIdCfg)) {
    
    
			throw new RuntimeException("查询不到此微信商户号ApiV3配置:" + merchantId);
		}

		WxPayMrchCfg wxPayMrchCfg = JSON.parseObject(merchantIdCfg, WxPayMrchCfg.class);

		if (!doCheckParam(merchantId, wxPayMrchCfg.getCertSerialNo(), wxPayMrchCfg.getPartnerKey(),
				wxPayMrchCfg.getCertPrivateKeyPath())) {
    
    
			throw new RuntimeException("初始化 CloseableHttpClient 失败,缺少必要参数");
		}
		return wxPayMrchCfg;
	}

	private Verifier getVerifier(String merchantId, String merchantSerialNumber, String apiV3Key,
			PrivateKey merchantPrivateKey)
			throws IOException, GeneralSecurityException, HttpCodeException, NotFoundException {
    
    
		Verifier verifier;
		try {
    
    
			// 从证书管理器中获取verifier
			verifier = certificatesManager.getVerifier(merchantId);
		} catch (IllegalArgumentException | NotFoundException e) {
    
    
			// 向证书管理器增加需要自动更新平台证书的商户信息
			certificatesManager.putMerchant(merchantId,
					new WechatPay2Credentials(merchantId,
							new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
					apiV3Key.getBytes(StandardCharsets.UTF_8));
			// 从证书管理器中获取verifier
			verifier = certificatesManager.getVerifier(merchantId);
		} catch (Exception e) {
    
    
			throw new RuntimeException("获取verifier失败");
		}
		return verifier;
	}

	private Optional<CloseableHttpClient> getHttpClient(String merchantId, String merchantSerialNumber,
			PrivateKey merchantPrivateKey, Verifier verifier) {
    
    
		if (MERCHANT_ID_HTTP_CLIENT_MAP.get(merchantId) == null) {
    
    
			synchronized (WxApiV3Utils.class) {
    
    
				if (MERCHANT_ID_HTTP_CLIENT_MAP.get(merchantId) == null) {
    
    
					// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
					CloseableHttpClient newHttpClient = WechatPayHttpClientBuilder.create()
							.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
							.withValidator(new WechatPay2Validator(verifier)).build();
					MERCHANT_ID_HTTP_CLIENT_MAP.put(merchantId, newHttpClient);
				}
			}
		}
		return Optional.ofNullable(MERCHANT_ID_HTTP_CLIENT_MAP.get(merchantId));
	}

	public BizResponse request(CloseableHttpClient httpClient, HttpPost httpPost, String reqMethodName)
			throws IOException {
    
    
		RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000).setConnectionRequestTimeout(2000)
				.setSocketTimeout(5000).build();
		httpPost.setConfig(requestConfig);

		BizResponse bizResponse;
		int statusCode;
		// 完成签名并执行请求
		try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
    
    
			statusCode = response.getStatusLine().getStatusCode();
			if (statusCode == 200) {
    
     // 处理成功
				bizResponse = BizResponse.success(EntityUtils.toString(response.getEntity()));
			} else if (statusCode == 204) {
    
    // 处理成功,无返回Body
				bizResponse = BizResponse.success("success");
			} else {
    
    
				bizResponse = BizResponse.failure(EntityUtils.toString(response.getEntity()));
			}
		}
		logger.info("{} 返回状态码:{},数据:{}", reqMethodName, statusCode, bizResponse.getMsg());
		return bizResponse;
	}

	/**
	 * 获取微信支付平台证书序列号
	 */
	public String getPlatformCertificateSerial(String merchantId) throws NotFoundException {
    
    
		return certificatesManager.getVerifier(merchantId).getValidCertificate().getSerialNumber().toString(16);
	}

	/**
	 * 敏感信息加密
	 */
	public String sensitiveInfoEncrypt(String merchantId, String text) throws Exception {
    
    
		Verifier verifier = CertificatesManager.getInstance().getVerifier(merchantId);
		X509Certificate certificate = verifier.getValidCertificate();
		return RsaCryptoUtil.encryptOAEP(text, certificate);
	}

	private boolean doCheckParam(String mrchId, String certSerialNo, String apiV3Key, String privateKeyFilePath) {
    
    
		return doCheckValue(mrchId, certSerialNo, apiV3Key, privateKeyFilePath);
	}

	private boolean doCheckValue(String... item) {
    
    
		for (String param : item) {
    
    
			if (StrUtil.isBlank(param)) {
    
    
				return false;
			}
		}
		return true;
	}

}

例如请求支付即服务的api可以看下面示例


/**
 * 支付即服务 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_1.shtml
 * 
 */
@Component
public class WxSmartGuideApi {
    
    
	private static final Logger logger = LoggerFactory.getLogger(WxSmartGuideApi.class);

	@Autowired
	private WxApiV3Utils wxApiV3Utils;

	/**
	 * 服务人员分配
	 *
	 * @param merchantId
	 *            商户id
	 * @param guideId
	 *            服务人员ID
	 * @param outOrderNo
	 *            商户订单号
	 */
	public BizResponse assignGuide(String merchantId, String guideId, String outOrderNo) throws Exception {
    
    
		CloseableHttpClient httpClient = wxApiV3Utils.getHttpClient(merchantId);

		// 请求URL
		HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/smartguide/guides/" + guideId + "/assign");

		// 请求body参数
		JSONObject body = new JSONObject();
		body.put("out_trade_no", outOrderNo);

		logger.info("服务人员分配 url:{},body:{}", httpPost.getURI(), body);

		initHttpPost(body, httpPost);

		return wxApiV3Utils.request(httpClient, httpPost, "服务人员分配");
	}

	private void initHttpPost(JSONObject body, HttpPost httpPost) {
    
    
		StringEntity entity = new StringEntity(body.toString(), "utf-8");
		entity.setContentType("application/json");

		httpPost.setEntity(entity);
		httpPost.setHeader("Accept", "application/json");
	}

	/**
	 * 服务人员注册
	 */
	public BizResponse regSmartGuide(String merchantId, String corpId, int storeId, String userId, String mobile,
			String qrCode, String avatar, String name) throws Exception {
    
    

		CloseableHttpClient httpClient = wxApiV3Utils.getHttpClient(merchantId);

		// 请求URL
		HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/smartguide/guides");

		// 请求body参数
		JSONObject body = new JSONObject();
		body.put("corpid", corpId);
		body.put("store_id", storeId);
		body.put("userid", userId);
		body.put("name", wxApiV3Utils.sensitiveInfoEncrypt(merchantId, name));
		body.put("mobile", wxApiV3Utils.sensitiveInfoEncrypt(merchantId, mobile));
		body.put("qr_code", qrCode);
		body.put("avatar", avatar);

		logger.info("服务人员注册 url:{},body:{}", httpPost.getURI(), body);

		initHttpPost(body, httpPost);

		String wechatpaySerial = wxApiV3Utils.getPlatformCertificateSerial(merchantId);
		httpPost.setHeader("Wechatpay-Serial", wechatpaySerial);

		return wxApiV3Utils.request(httpClient, httpPost, "服务人员注册");
	}

}

猜你喜欢

转载自blog.csdn.net/Fire_Sky_Ho/article/details/128006925