feat(wxapi): 反序列化回调通知事件时自动判定是否需要解密消息,并调整序列化回调通知应答默认为安全模式

This commit is contained in:
Fu Diwei 2022-02-28 21:20:04 +08:00
parent 120315f903
commit 23d9d247ef
2 changed files with 26 additions and 37 deletions

View File

@ -9,7 +9,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
/// </summary> /// </summary>
public static class WechatApiClientEventExtensions public static class WechatApiClientEventExtensions
{ {
private class EncryptedWechatApiEvent private class InnerEncryptedEvent
{ {
[Newtonsoft.Json.JsonProperty("Encrypt")] [Newtonsoft.Json.JsonProperty("Encrypt")]
[System.Text.Json.Serialization.JsonPropertyName("Encrypt")] [System.Text.Json.Serialization.JsonPropertyName("Encrypt")]
@ -18,7 +18,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
[Newtonsoft.Json.JsonProperty("TimeStamp")] [Newtonsoft.Json.JsonProperty("TimeStamp")]
[System.Text.Json.Serialization.JsonPropertyName("TimeStamp")] [System.Text.Json.Serialization.JsonPropertyName("TimeStamp")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalStringConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalStringConverter))]
public string Timestamp { get; set; } = default!; public string TimestampString { get; set; } = default!;
[Newtonsoft.Json.JsonProperty("Nonce")] [Newtonsoft.Json.JsonProperty("Nonce")]
[System.Text.Json.Serialization.JsonPropertyName("Nonce")] [System.Text.Json.Serialization.JsonPropertyName("Nonce")]
@ -29,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
public string Signature { get; set; } = default!; public string Signature { get; set; } = default!;
} }
private static TEvent InnerDeserializeEventFromJson<TEvent>(this WechatApiClient client, string callbackJson, bool safety) private static TEvent InnerDeserializeEventFromJson<TEvent>(this WechatApiClient client, string callbackJson)
where TEvent : WechatApiEvent where TEvent : WechatApiEvent
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client == null) throw new ArgumentNullException(nameof(client));
@ -37,15 +37,9 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
try try
{ {
if (safety) if (callbackJson.Contains("\"Encrypt\""))
{ {
if (string.IsNullOrEmpty(client.Credentials.PushEncodingAESKey)) InnerEncryptedEvent encryptedEvent = client.JsonSerializer.Deserialize<InnerEncryptedEvent>(callbackJson);
throw new Exceptions.WechatApiEventSerializationException("Encrypt event failed, because there is no encoding AES key.");
var encryptedEvent = client.JsonSerializer.Deserialize<EncryptedWechatApiEvent>(callbackJson);
if (string.IsNullOrEmpty(encryptedEvent.EncryptedData))
throw new Exceptions.WechatApiEventSerializationException("Encrypt event failed, because of empty encrypted data.");
callbackJson = Utilities.WxBizMsgCryptor.AESDecrypt(cipherText: encryptedEvent.EncryptedData, encodingAESKey: client.Credentials.PushEncodingAESKey!, out _); callbackJson = Utilities.WxBizMsgCryptor.AESDecrypt(cipherText: encryptedEvent.EncryptedData, encodingAESKey: client.Credentials.PushEncodingAESKey!, out _);
} }
@ -61,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
} }
} }
private static TEvent InnerDeserializeEventFromXml<TEvent>(this WechatApiClient client, string callbackXml, bool safety) private static TEvent InnerDeserializeEventFromXml<TEvent>(this WechatApiClient client, string callbackXml)
where TEvent : WechatApiEvent where TEvent : WechatApiEvent
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client == null) throw new ArgumentNullException(nameof(client));
@ -69,12 +63,11 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
try try
{ {
if (safety) if (callbackXml.Contains("<Encrypt>") && callbackXml.Contains("</Encrypt>"))
{ {
if (!Utilities.WxBizMsgCryptor.TryParseXml(callbackXml, out string? encryptedXml)) XDocument xDocument = XDocument.Parse(callbackXml);
throw new Exceptions.WechatApiEventSerializationException("Encrypt event failed, because of empty encrypted data."); string encryptedData = xDocument.Root.Element("Encrypt").Value;
callbackXml = Utilities.WxBizMsgCryptor.AESDecrypt(cipherText: encryptedData, encodingAESKey: client.Credentials.PushEncodingAESKey!, out _);
callbackXml = Utilities.WxBizMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.PushEncodingAESKey!, out _);
} }
return Utilities.XmlUtility.Deserialize<TEvent>(callbackXml); return Utilities.XmlUtility.Deserialize<TEvent>(callbackXml);
@ -95,12 +88,11 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
/// <typeparam name="TEvent"></typeparam> /// <typeparam name="TEvent"></typeparam>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackJson"></param> /// <param name="callbackJson"></param>
/// <param name="safety">是否是安全模式(即是否需要解密)。</param>
/// <returns></returns> /// <returns></returns>
public static TEvent DeserializeEventFromJson<TEvent>(this WechatApiClient client, string callbackJson, bool safety = false) public static TEvent DeserializeEventFromJson<TEvent>(this WechatApiClient client, string callbackJson)
where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IJsonSerializable, new() where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IJsonSerializable, new()
{ {
return InnerDeserializeEventFromJson<TEvent>(client, callbackJson, safety); return InnerDeserializeEventFromJson<TEvent>(client, callbackJson);
} }
/// <summary> /// <summary>
@ -108,11 +100,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackJson"></param> /// <param name="callbackJson"></param>
/// <param name="safety">是否是安全模式(即是否需要解密)。</param>
/// <returns></returns> /// <returns></returns>
public static WechatApiEvent DeserializeEventFromJson(this WechatApiClient client, string callbackJson, bool safety = false) public static WechatApiEvent DeserializeEventFromJson(this WechatApiClient client, string callbackJson)
{ {
return InnerDeserializeEventFromJson<WechatApiEvent>(client, callbackJson, safety); return InnerDeserializeEventFromJson<WechatApiEvent>(client, callbackJson);
} }
/// <summary> /// <summary>
@ -121,12 +112,11 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
/// <typeparam name="TEvent"></typeparam> /// <typeparam name="TEvent"></typeparam>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackXml"></param> /// <param name="callbackXml"></param>
/// <param name="safety">是否是安全模式(即是否需要解密)。</param>
/// <returns></returns> /// <returns></returns>
public static TEvent DeserializeEventFromXml<TEvent>(this WechatApiClient client, string callbackXml, bool safety = false) public static TEvent DeserializeEventFromXml<TEvent>(this WechatApiClient client, string callbackXml)
where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IXmlSerializable, new() where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IXmlSerializable, new()
{ {
return InnerDeserializeEventFromXml<TEvent>(client, callbackXml, safety); return InnerDeserializeEventFromXml<TEvent>(client, callbackXml);
} }
/// <summary> /// <summary>
@ -134,11 +124,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackXml"></param> /// <param name="callbackXml"></param>
/// <param name="safety">是否是安全模式(即是否需要解密)。</param>
/// <returns></returns> /// <returns></returns>
public static WechatApiEvent DeserializeEventFromXml(this WechatApiClient client, string callbackXml, bool safety = false) public static WechatApiEvent DeserializeEventFromXml(this WechatApiClient client, string callbackXml)
{ {
return InnerDeserializeEventFromXml<WechatApiEvent>(client, callbackXml, safety); return InnerDeserializeEventFromXml<WechatApiEvent>(client, callbackXml);
} }
/// <summary> /// <summary>
@ -149,7 +138,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
/// <param name="callbackModel"></param> /// <param name="callbackModel"></param>
/// <param name="safety">是否是安全模式(即是否需要加密)。</param> /// <param name="safety">是否是安全模式(即是否需要加密)。</param>
/// <returns></returns> /// <returns></returns>
public static string SerializeEventToJson<TEvent>(this WechatApiClient client, TEvent callbackModel, bool safety = false) public static string SerializeEventToJson<TEvent>(this WechatApiClient client, TEvent callbackModel, bool safety = true)
where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IJsonSerializable, new() where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IJsonSerializable, new()
{ {
string json; string json;
@ -186,10 +175,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
sMsgEncrypt: cipher sMsgEncrypt: cipher
); );
json = client.JsonSerializer.Serialize(new EncryptedWechatApiEvent() json = client.JsonSerializer.Serialize(new InnerEncryptedEvent()
{ {
EncryptedData = cipher, EncryptedData = cipher,
Timestamp = timestamp, TimestampString = timestamp,
Nonce = nonce, Nonce = nonce,
Signature = sign Signature = sign
}); });
@ -211,7 +200,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
/// <param name="callbackModel"></param> /// <param name="callbackModel"></param>
/// <param name="safety">是否是安全模式(即是否需要加密)。</param> /// <param name="safety">是否是安全模式(即是否需要加密)。</param>
/// <returns></returns> /// <returns></returns>
public static string SerializeEventToXml<TEvent>(this WechatApiClient client, TEvent callbackModel, bool safety = false) public static string SerializeEventToXml<TEvent>(this WechatApiClient client, TEvent callbackModel, bool safety = true)
where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IXmlSerializable, new() where TEvent : WechatApiEvent, WechatApiEvent.Serialization.IXmlSerializable, new()
{ {
string xml; string xml;
@ -289,7 +278,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
try try
{ {
var encryptedEvent = client.JsonSerializer.Deserialize<EncryptedWechatApiEvent>(callbackJson); var encryptedEvent = client.JsonSerializer.Deserialize<InnerEncryptedEvent>(callbackJson);
return Utilities.WxBizMsgCryptor.VerifySignature( return Utilities.WxBizMsgCryptor.VerifySignature(
sToken: client.Credentials.PushToken!, sToken: client.Credentials.PushToken!,
sTimestamp: callbackTimestamp, sTimestamp: callbackTimestamp,

View File

@ -4,8 +4,8 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
{ {
public class TestCase_EventVerificationTests public class TestCase_EventVerificationTests
{ {
[Fact(DisplayName = "测试用例:验签并解密回调数据")] [Fact(DisplayName = "测试用例:验签并解密 XML 回调数据")]
public void TestVerifyEvent() public void TestVerifyXmlEvent()
{ {
string callbacMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6"; string callbacMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6";
string callbacTimeStamp = "1409659813"; string callbacTimeStamp = "1409659813";
@ -19,7 +19,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
PushToken = "QDG6eK" PushToken = "QDG6eK"
}; };
var client = new WechatApiClient(options); var client = new WechatApiClient(options);
var eventModel = client.DeserializeEventFromXml<Events.TextMessageEvent>(callbackXml, safety: true); var eventModel = client.DeserializeEventFromXml<Events.TextMessageEvent>(callbackXml);
Assert.True(client.VerifyEventSignatureFromXml(callbacTimeStamp, callbacNonce, callbackXml, callbacMsgSig)); Assert.True(client.VerifyEventSignatureFromXml(callbacTimeStamp, callbacNonce, callbackXml, callbacMsgSig));
Assert.Equal("text", eventModel.MessageType, ignoreCase: true); Assert.Equal("text", eventModel.MessageType, ignoreCase: true);