支付系统实战-微信平台证书、APIv3密钥正确的使用方式

微信平台证书获取

为确保 API 请求过程中的安全性,客户端需要使用微信支付平台证书来验证服务器响应的真实性和完整性。微信平台证书需要我们下载来使用,主要应用场景是商户用来验证签名、微信侧来解密数据使用。

服务端的sdk已经为我们提供了下载证书的api。打开服务端sdk的源码地址:https://github.com/wechatpay-apiv3/wechatpay-java,我们可以通过手动或自动下载证书的方式来获取微信平台证书,如果我们使用的自动下载证书 从 v0.2.3 版本开始,sdk中引入了一个名为 RSAAutoCertificateConfig 的配置类,用于自动更新平台证书。

RSAAutoCertificateConfig 会利用 AutoCertificateService 自动下载微信支付平台证书。 AutoCertificateService 将启动一个后台线程,定期(目前为每60分钟)更新证书,以实现证书过期时的平滑切换。

在每次构建 RSAAutoCertificateConfig 时,SDK 首先会使用传入的商户参数下载一次微信支付平台证书。如果下载成功,SDK 会将商户参数注册或更新至 AutoCertificateService。若下载失败,将会抛出异常。

为了提高性能,建议将配置类作为全局变量。复用 RSAAutoCertificateConfig 可以减少不必要的证书下载,避免资源浪费。只有在配置发生变更时,才需要重新构造RSAAutoCertificateConfig

如果您有多个商户号,可以为每个商户构建相应的 RSAAutoCertificateConfig。为了保证是全局唯一,下面我们来初始化一个全局配置类,我们使用单例模式来自动更新证书代码如下:创建一个WxInitUtils.java文件,添加如下代码:

//商户的全局配置类
private static Config instance;

/**
* 定义商户的全局配置信息,要求一个商户号对应一个配置
* 不能重复生成配置
* RSAAutoCertificateConfig 会利用 AutoCertificateService 自动下载微信支付平台证书。
* AutoCertificateService 将启动一个后台线程,定期(目前为每60分钟)更新证书,
* 以实现证书过期时的平滑切换。
* 在每次构建 RSAAutoCertificateConfig 时,
* SDK 首先会使用传入的商户参数下载一次微信支付平台证书。如果下载成功,SDK 会将商户参数注册或更
* 新至 AutoCertificateService。若下载失败,将会抛出异常。
* 为了提高性能,建议将配置类作为全局变量。复用 RSAAutoCertificateConfig
* 可以减少不必要的证书下载,避免资源浪费。只有在配置发生变更时,
* 才需要重新构造 RSAAutoCertificateConfig。
*/

public static Config getInstance(WxPayConfig wxPayConfig){
if (instance == null){
//如果实例不存在,创建一个新的实例
synchronized (WxPayConfig.class){
//双重检查锁定,防止多线程竞争时创建多个实例
if (instance == null){
try{
if(wxPayConfig == null){
log.info("配置信息加载出错===");
return null;
}
log.info("商户号为==="+ wxPayConfig.getMchId());
log.info("商户私钥串为==="+ wxPayConfig.getPrivateKey());
log.info("序列号为==="+ wxPayConfig.getMchSerialNo());
log.info("密钥为==="+ wxPayConfig.getApiV3Key());
instance = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMchId())
.privateKey(wxPayConfig.getPrivateKey())
// .privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
.merchantSerialNumber(wxPayConfig.getMchSerialNo())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
}catch (Exception e){
e.printStackTrace();
log.error("构建商户配置信息出错,错误信息为"+e.getMessage());
return null;
}
}
}
}
return instance;
}

上述代码instance实例只会初始化一次。调用getInstance方法并传入我们申请证书信息即可完成证书的自动更新和下载。

接下来我们从源码分析的角度上来分析一下证书的下载过程,分析下载过程主要目的是看看上一节的密钥证书是如何应用的。我们重点来分析这段代码块:

instance = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMchId())
.privateKey(wxPayConfig.getPrivateKey())
.merchantSerialNumber(wxPayConfig.getMchSerialNo())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();

使用 new RSAAutoCertificateConfig.Builder() 拿到其内部静态类Builder,Builder类继承了抽象类 AbstractRSAConfigBuilder,并调用父类的方法来初始化商户信息放到AbstractRSAConfigBuilder配置类中。调用其内部的build方法构造RSAAutoCertificateProvider.Builder类,同时将商户信息存储到RSAAutoCertificateProvider中,并调用build()方法来下载证书,下载证书的具体步骤:
1、首先租组装商户的签名信息(包括商户证书的序列号、商户私钥、商户号)如下代码段:

credential =
new WechatPay2Credential(
requireNonNull(merchantId),
new RSASigner(requireNonNull(merchantSerialNumber), privateKey));
}

2、构造httpClient,并将商户证书、签名签证器设置到httpclient中。
3、请求微信平台的证书下载地址,进行证书下载,构造证书下载器:

CertificateDownloader downloader =
new CertificateDownloader.Builder()
.certificateHandler(rsaCertificateHandler)
.downloadUrl(REQUEST_URL)
.aeadCipher(aeadCipher)
.httpClient(httpClient)
.build();

其中 REQUEST_URL = "https://api.mch.weixin.qq.com/v3/certificates?algorithm_type=RSA"aeadCipher 为APIv3密钥。构造的httpClient


4、证书下载:

/**
* 注册证书下载任务 如果是第一次注册,会先下载证书。如果能成功下载,再保存下载器,供定时更新证书使用。如果下载失败,会抛出异常。
* 如果已经注册过,当前传入的下载器将覆盖之前的下载器。如果当前下载器不能下载证书,定时更新证书会失败。
*
* @param merchantId 商户号
* @param type 调用方自定义的证书类型,例如 RSA/ShangMi
* @param downloader 证书下载器
*/

public static void register(String merchantId, String type, CertificateDownloader downloader) {
String key = calculateDownloadWorkerMapKey(merchantId, type);
Runnable worker =
() -> {
Map<String, X509Certificate> result = downloader.download();
certificateMap.put(key, result);
};

// 下载证书,以验证配置是正确的
// 如果错误将抛出异常,fast-fail
worker.run();
// 更新配置
downloadWorkerMap.put(key, worker);

start(defaultUpdateInterval);
}

打开download方法:

/** 下载证书 */
public Map<String, X509Certificate> download() {
HttpRequest httpRequest =
new HttpRequest.Builder()
.httpMethod(HttpMethod.GET)
.url(downloadUrl)
.addHeader(Constant.ACCEPT, " */*")
.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue())
.build();
HttpResponse<DownloadCertificateResponse> httpResponse =
httpClient.execute(httpRequest, DownloadCertificateResponse.class);

Map<String, X509Certificate> downloaded = decryptCertificate(httpResponse);
validateCertificate(downloaded);
return downloaded;
}

这里大家注意,重点来了,在下载证书时,sdk将签名封装到了httpClient中,在HttpResponse<DownloadCertificateResponse> httpResponse = httpClient.execute(httpRequest, DownloadCertificateResponse.class); 这段代码中的execute方法中通过构造token值,并将token放到请求的header中,重点看这个方法getAuthorization(httpRequest) 这个方法中,使用了商户的私钥将请求body进行签名,并将明文、商户的公钥、签名信息组成token串发送给了微信平台,具体构造token的代码块为:

String token =
"mchid=""
+ getMerchantId()
+ "","
+ "nonce_str=""
+ nonceStr
+ "","
+ "timestamp=""
+ timestamp
+ "","
+ "serial_no=""
+ signature.getCertificateSerialNumber()
+ "","
+ "signature=""
+ signature.getSign()
+ """;

请求完成后,微信平台侧会实时将响应数据返回。在商户侧调用decryptCertificate(httpResponse) 将响应的数据进行解密,解密时使用我们之前设置的APIv3密钥。紧接着会验证下载证书的有效性会调用validateCertificate(downloaded);具体的验证代码段如下:

PKIXParameters params = new PKIXParameters(trustAnchor);
params.setRevocationEnabled(false);

List<X509Certificate> certs = new ArrayList<>();
certs.add(certificate);

CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath certPath = cf.generateCertPath(certs);

CertPathValidator validator = CertPathValidator.getInstance("PKIX");
validator.validate(certPath, params);

上述代码块用于检查证书是否由可信的根证书颁发,以及证书是否有效。X509Certificate是一种常用的数字证书标准。首先创建一个 PKIXParameters 类型的对象,它是一个用于验证证书路径的参数集合,它的构造器需要一个 trustAnchor 参数,表示可信的根证书,它是一个 Set 类型的对象,它包含了一个或多个根证书的信息。getInstance(“PKIX”) 返回一个支持 PKIX(公钥基础设施)算法的验证器实例。

证书下载完成会存放到 AutoCertificateService类certificateMap中 ,certificateMap 是一个ConcurrentHashMap ,是线程安全的,可以支持多线程的并发访问和修改,而AutoCertificateService类是一个定时更新证书的服务,它是一个由静态函数构成的工具类。

private static final ConcurrentHashMap<String, Map<String, X509Certificate>> certificateMap =
new ConcurrentHashMap<>();
Map<String, X509Certificate> result = downloader.download();
certificateMap.put(key, result);

到此微信平台证书的自动下载过程就描述清楚了,下面我们来总结一下具体流程:

(1)商户侧包装商户信息:商户号、商户公钥、商户私钥等信息,并将请求的数据进行签名。将签名和公钥、时间戳、随机数等信息打包成token放到请求头部发送至微信平台侧。
(2)微信测收到请求后获取到认证token,并解析出商户公钥和明文信息,对数据进行验签。同时使用APIv3密钥将证书进行加密同步返回给商户侧。
(3)商户侧收到微信侧的响应数据后,从响应数据解密出证书,应答报文解密后,生成X.509证书对象,也就是将证书从String转为X509Certificate。
(4)验证证书的有效性
(5)将证书存储到AutoCertificateService中的certificateMap 中。

APIv3密钥设置

在商户平台中【微信支付商户平台 – 账户中心 – 账户设置 – API安全 – APIv3密钥设置】,这个可使用在线生成器生成地址:https://www.bchrt.com/tools/suijimima/

商户平台证书和私钥的获取

以下内容来自微信官网:https://kf.qq.com/faq/161222NneAJf161222U7fARv.html
1、登录【微信支付商户平台 – 账户中心 – 账户设置 – API安全 – 申请API证书】申请证书,确定后请勿关闭页面


支付系统实战-微信平台证书、APIv3密钥正确的使用方式
支付系统实战-微信平台证书、APIv3密钥正确的使用方式

2、点击下载证书工具;下载后,双击“WXCertUtil.exe”文件,选择安装路径后,点击申请证书

也可通过以下链接下载证书工具:

windows版本
mac版本

3、在【证书工具】,填写商户号信息(商户号、商户名称),点击下一步


支付系统实战-微信平台证书、APIv3密钥正确的使用方式

4、在【证书工具】,复制证书请求串
(若提示”请粘贴请求串到商户平台获取证书串”,请在第5点步骤检查是否已粘贴。可同时尝试手动鼠标复制粘贴的方法)


支付系统实战-微信平台证书、APIv3密钥正确的使用方式

5、在【商户平台】,粘贴证书请求串


支付系统实战-微信平台证书、APIv3密钥正确的使用方式

6、在【商户平台】,输入操作密码,安全验证后生成证书串


支付系统实战-微信平台证书、APIv3密钥正确的使用方式

7、在【商户平台】,复制证书串


支付系统实战-微信平台证书、APIv3密钥正确的使用方式

8、在【证书工具】,粘贴证书串,点击下一步,申请证书成功
(若提示”证书与本地公私钥不匹配”,可能是浏览器禁用了剪切板复制功能。请在操作步骤第7点,操作时使用鼠标选中全部证书串内容(注意右边有下拉框),单击鼠标右键选择复制)


支付系统实战-微信平台证书、APIv3密钥正确的使用方式
支付系统实战-微信平台证书、APIv3密钥正确的使用方式

提醒:请将生成的证书文件转交给技术人员,由技术人员将证书部署到服务器上(请务必妥善保管证书及私钥,因为私钥文件只能通过证书工具导出,若私钥丢失,则无法找回,只能作废后重新申请。)

9、证书申请成功后,在证书文件夹中解压文件会发现有3个文件:.p12 这个不用管,其中两个文件apiclient_cert.pem(商户公钥文件)、apiclient_key(商户私钥文件)。

到此,商户API证书已经获取完毕。

初始化微信配置

为了统一配置微信支付用到的信息,我们在项目的resource目录下创建一个配置文件wxpay.properties

# 商户号
wxpay.mch-id=xxxxxxx
# 微信商户证书序列号
wxpay.mch-serial-no=xxxxxxxxxxxxxxxxxxx
# 商户私钥(路径加载方式使用)
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=xxxxxxxxxxxxxx
# 小程序APPID
wxpay.appid=xxxxxxxxxxx
# 微信小程序密钥
wxpay.appSecret=xxxxxxxxxxxxxxxxxxxxxxxxx
# 微信商户平台域名(调用接口时的通用域名)
wxpay.domain=https://api.mch.weixin.qq.com
#微信支付回调地址域名(需要https 并且为备案的域名)
wxpay.notify-domain=https://test.notify.com
#商户私钥
wxpay.private-key=-----BEGIN PRIVATE KEY-----xxxx-----END PRIVATE KEY-----

其对应的配置类如下:

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
public class WxPayConfig {

// 商户号
private String mchId;

// 商户API证书序列号
private String mchSerialNo;

// 商户私钥文件
private String privateKeyPath;

// APIv3密钥
private String apiV3Key;

// APPID
private String appid;
//小程序密钥
private String appSecret;
// 微信服务器地址
private String domain;

// 接收结果通知地址
private String notifyDomain;

// APIv2密钥
private String partnerKey;

//商户私钥字符串
private String privateKey;
}


原文始发于微信公众号(小核桃编程):支付系统实战-微信平台证书、APIv3密钥正确的使用方式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/216000.html

(0)
李, 若俞的头像李, 若俞

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!