• 微信加解密流程,证书作用讲解,官方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;
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142

    例如请求支付即服务的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, "服务人员注册");
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
  • 相关阅读:
    戴尔PowerEdge R650服务器荣获国家级实验室5项证书
    显存优化综述
    蓝桥杯:买不到的数目
    【人工智能 | 认知观与系统类别】从宏观角度看人工智能认知观与系统类别:探索人工智能无垠领域
    docker network网络模式
    显卡核心 短路 原因是什么?
    0基础学习PyFlink——使用Table API实现SQL功能
    iOS小技能:安全措施
    CPT203-Software Engineering
    移动开发技术
  • 原文地址:https://blog.csdn.net/Fire_Sky_Ho/article/details/128006925