From ebe14b08f88919fdab27adf32ae2d228413c51ec Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 20 Jul 2021 15:35:04 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Advanced_EventDataDecryption.md | 15 +++-- ...Advanced_EventDataSignatureVerification.md | 17 ++++++ .../Advanced_ResponseDataDecryption.md | 4 +- .../Advanced_ResponseSignatureVerification.md | 59 ++++++++++++++++--- docs/WechatTenpayV3/README.md | 11 +++- 5 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 docs/WechatTenpayV3/Advanced_EventDataSignatureVerification.md diff --git a/docs/WechatTenpayV3/Advanced_EventDataDecryption.md b/docs/WechatTenpayV3/Advanced_EventDataDecryption.md index 0c10673d..5e9269f5 100644 --- a/docs/WechatTenpayV3/Advanced_EventDataDecryption.md +++ b/docs/WechatTenpayV3/Advanced_EventDataDecryption.md @@ -6,19 +6,22 @@ > > [《微信支付开发者文档 - 开发指南:证书和回调报文解密》](https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_2.shtml) -对于回调通知事件的敏感信息,微信支付平台使用了商户公钥基于 RSA 算法加密。 +对于回调通知事件的敏感信息,微信商户平台使用了商户公钥基于 RSA 算法加密。 -开发者利用本库提供的 `RSAUtility` 工具类自行解密相关字段。 +开发者可利用本库提供的 `RSAUtility` 工具类自行解密相关字段。 此外,本库还封装了直接解密事件的扩展方法,下面给出一个示例: ```csharp -string callbackJson = "{ ... }"; // 微信支付平台发来的通知内容 - -var callbackModel = client.DeserializeEvent(callbackJson); // 得到通知对象 +/* 微信商户平台发来的通知内容 */ +string callbackJson = "{ ... }"; +/* 将 JSON 反序列化得到通知对象 */ +/* 你也可以将 WechatTenpayEvent 类型直接绑定到 MVC 模型上,这样就不再需要手动反序列化 */ +var callbackModel = client.DeserializeEvent(callbackJson); if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType)) { - var callbackResource = client.DecryptEventResource(callbackModel); // 得到支付通知敏感数据 + /* 根据事件类型,解密得到支付通知敏感数据 */ + var callbackResource = client.DecryptEventResource(callbackModel); string outTradeNumber = callbackResource.OutTradeNumber; string transactionId = callbackResource.TransactionId; } diff --git a/docs/WechatTenpayV3/Advanced_EventDataSignatureVerification.md b/docs/WechatTenpayV3/Advanced_EventDataSignatureVerification.md new file mode 100644 index 00000000..baa97dc1 --- /dev/null +++ b/docs/WechatTenpayV3/Advanced_EventDataSignatureVerification.md @@ -0,0 +1,17 @@ +### 如何验证回调通知事件签名? + +--- + +验证回调通知事件签名的方法与验证响应签名的类似,请参阅[相关文档](./Advanced_ResponseSignatureVerification.md)。 + +同样的,你既然可以利用本库提供的 `RSAUtility` 工具类自行进行签名验证,也可以通过 `ICertificateStorer` 尝试自动完成签名验证: + +```csharp +bool ret = client.VerifyEventSignature( + callbackTimestamp: "微信回调通知中的 Wechatpay-Timestamp 字段", + callbackNonce: "微信回调通知中的 Wechatpay-Nonce 字段", + callbackBody: "微信回调通知中请求正文", + callbackSignature: "微信回调通知中的 Wechatpay-Signature 字段", + callbackSerialNumber: "微信回调通知中的 Wechatpay-Serial 字段" +); +``` diff --git a/docs/WechatTenpayV3/Advanced_ResponseDataDecryption.md b/docs/WechatTenpayV3/Advanced_ResponseDataDecryption.md index c3d97376..650c1980 100644 --- a/docs/WechatTenpayV3/Advanced_ResponseDataDecryption.md +++ b/docs/WechatTenpayV3/Advanced_ResponseDataDecryption.md @@ -8,7 +8,7 @@ > > [《微信支付开发者文档 - 开发指南:敏感信息加解密》](https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_3.shtml) -对于部分接口返回的敏感信息,微信支付平台可能会使用两种方式进行加密: +对于部分接口返回的敏感信息,微信商户平台可能会使用两种方式进行加密: - 使用商户公钥基于 RSA 算法加密。 @@ -23,6 +23,6 @@ var request = new Models.QueryCertificatesRequest(); var response = await client.ExecuteQueryCertificatesAsync(request); string cert = response.CertificateList.First().EncryptCertificate.CipherText; // 此时仍是密文 -client.DecryptResponseEncryptedData(response); +client.DecryptResponseEncryptedData(ref response); string cert = response.CertificateList.First().EncryptCertificate.CipherText; // 此时已是明文 ``` diff --git a/docs/WechatTenpayV3/Advanced_ResponseSignatureVerification.md b/docs/WechatTenpayV3/Advanced_ResponseSignatureVerification.md index eea0bc18..aee93fe1 100644 --- a/docs/WechatTenpayV3/Advanced_ResponseSignatureVerification.md +++ b/docs/WechatTenpayV3/Advanced_ResponseSignatureVerification.md @@ -1,4 +1,4 @@ -### 如何验证微信响应签名? +### 如何验证响应签名? --- @@ -8,18 +8,63 @@ > > [《微信支付开发者文档 - 平台证书:更新指引》](https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_0.shtml) -本库已经封装了 `QueryCertificatesAsync()` 方法,供开发者获取微信支付平台证书。 +验签过程中需要使用的微信商户平台证书,开发者可通过本库封装的 `QueryCertificatesAsync()` 方法来获取。 -每个响应对象均包含 `WechatpayTimestamp`、`WechatpayNonce`、`WechatpaySignature`、`WechatpaySerial` 等几个公共字段,你可根据官方文档的规则利用本库提供的 `RSAUtility` 工具类自行进行签名验证。 +每个响应对象均包含名为 `WechatpayTimestamp`、`WechatpayNonce`、`WechatpaySignature` 的几个公共字段,你可根据官方文档的规则利用本库提供的 `RSAUtility` 工具类自行进行签名验证。 -同时,本库也内置了验证签名的的方法,具体用法可以参考项目目录下的 _test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseVerificationTests.cs_ 文件给出的测试用例。 +具体用法可以参考项目目录下的 _test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseVerificationTests.cs_ 文件给出的测试用例。 -需要注意的是,微信支付平台提供的是 PEM 格式的密钥文件,分为以下几种: +--- + +#### 密钥文件格式说明: + +需要注意的是,微信商户平台提供的是 PEM 格式的密钥文件,分为以下几种: - 以 `-----BEGIN PRIVATE KEY-----` 开头、 `-----END PRIVATE KEY-----` 结尾的是 **PKCS#8 私钥**文件。 - 以 `-----BEGIN PUBLIC KEY-----` 开头、 `-----END PUBLIC KEY-----` 结尾的是 **PKCS#8 公钥**文件。 -- 以 `-----BEGIN CERTIFICATE-----` 开头、 `-----END CERTIFICATE-----` 结尾的是 **CER 证书**文件。 +- 以 `-----BEGIN CERTIFICATE--- --` 开头、 `-----END CERTIFICATE-----` 结尾的是 **CER 证书**文件。 -`QueryCertificatesAsync()` 方法返回的结果是 CER 证书,验证签名时需要先通过 `RSAUtility` 工具类导出 PKCS#8 公钥,再进行签名验证;当然,`RSAUtility` 也封装了直接通过 CER 证书验证签名的方法。 +谨记,`QueryCertificatesAsync()` 方法返回的结果是 CER 证书,需要先通过 `RSAUtility` 工具类导出 PKCS#8 公钥,再进行签名验证;当然,`RSAUtility` 也封装了直接通过 CER 证书验证签名的方法。 + +下面给出一个示例代码: + +```csharp +string data = "待验签的数据"; +string sign = "待验证的签名"; +string certificate = "CER 证书内容"; +/* 通过证书验证签名 */ +bool ret = RSAUtility.VerifyWithSHA256ByCertificate(certificate, data, sign); +/* 通过公钥验证签名 */ +string publicKey = RSAUtility.ExportPublicKey(certificate); +bool ret = RSAUtility.VerifyWithSHA256(publicKey, data, sign); +``` + +--- + +#### 通过 `ICertificateStorer` 接口存取平台证书信息: + +微信商户平台证书需要通过 API 的方式获取、且可能同时存在多个有效证书,本库提供了一个 `ICertificateStorer` 接口可用于存取证书信息。 + +你可以在构造得到 `WechatApiClient` 对象时指定证书存取器: + +```csharp +var certStorer = new InMemoryCertificateStorer(); // 为便于后续使用,该对象可使用全局单例的方式声明 +var options = new WechatTenpayClientOptions() { CertificateStorer = certStorer }; +var client = new WechatTenpayClient(options); +``` + +> 注:`InMemoryCertificateStorer` 是本库内置的基于内存实现的证书存取器;你也可实现 `ICertificateStorer` 接口,例如利用数据库或 Redis 等方式存取证书信息。 + +你应在后台周期性地调用 `QueryCertificatesAsync()` 方法,并在解密得到证书内容后,记录到证书存取器中: + +```csharp +certStorer.Set("CER 证书序列号", "CER 证书内容"); +``` + +每个响应对象会包含一个名为 `WechatpayCertSerialNumber` 的公共字段,本库会根据该字段的值自动尝试在证书存取器中读取证书内容,并完成响应签名的验证: + +```csharp +bool ret = client.VerifyResponseSignature(response); +``` diff --git a/docs/WechatTenpayV3/README.md b/docs/WechatTenpayV3/README.md index 24c72f6c..2e79991a 100644 --- a/docs/WechatTenpayV3/README.md +++ b/docs/WechatTenpayV3/README.md @@ -75,13 +75,16 @@ ```csharp using SKIT.FlurlHttpClient.Wechat; using SKIT.FlurlHttpClient.Wechat.TenpayV3; +using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; +var certStorer = new InMemoryCertificateStorer(); var options = new WechatTenpayClientOptions() { MerchantId = "微信商户号", MerchantV3Secret = "微信商户 v3 API 密钥", MerchantCertSerialNumber = "微信商户证书序列号", - MerchantCertPrivateKey = "-----BEGIN PRIVATE KEY-----微信商户证书私钥-----END PRIVATE KEY-----" + MerchantCertPrivateKey = "-----BEGIN PRIVATE KEY-----微信商户证书私钥-----END PRIVATE KEY-----", + CertificateStorer = certStorer }; var client = new WechatTenpayClient(options); ``` @@ -127,8 +130,6 @@ else - [如何查看商户证书序列号?](./Advanced_MerchantCertSerialNumber.md) -- [如何验证微信响应签名?](./Advanced_ResponseSignatureVerification.md) - - [如何快速找到需要调用的 API 模型类名 / 方法名(附完整 API 对照表)?](./Advanced_ModelDefinition.md) - [如何在 ASP.NET Core 中与 `IHttpClientFactory` 集成?](./Advanced_IHttpClientFactory.md) @@ -137,8 +138,12 @@ else - [如何使用拦截器?](./Advanced_Interceptor.md) +- [如何验证响应签名?](./Advanced_ResponseSignatureVerification.md) + - [如何解密响应中的敏感数据?](./Advanced_ResponseDataDecryption.md) +- [如何验证回调通知事件签名?](./Advanced_EventDataSignatureVerification.md) + - [如何解密回调通知事件中的敏感数据?](./Advanced_EventDataDecryption.md) - [如何生成客户端调起支付时所需的参数及签名?](./Advanced_Payment.md)