refactor(wxapi): clean code

This commit is contained in:
Fu Diwei
2024-05-22 22:24:29 +08:00
parent 42272049e5
commit 2e0ea22e6e
4 changed files with 28 additions and 22 deletions

View File

@@ -23,6 +23,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
* https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html
* https://developers.weixin.qq.com/community/develop/article/doc/00028ca675c708b23f100b8e161013 * https://developers.weixin.qq.com/community/develop/article/doc/00028ca675c708b23f100b8e161013
* https://developers.weixin.qq.com/community/develop/article/doc/000e68b8038ed8796f00f6c2f68c13 * https://developers.weixin.qq.com/community/develop/article/doc/000e68b8038ed8796f00f6c2f68c13
* https://wxaintpcos.wxqcloud.qq.com.cn/public/tmp/apitool.py
*/ */
private static readonly ISet<string> SIGN_REQUIRED_URLS = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private static readonly ISet<string> SIGN_REQUIRED_URLS = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
@@ -88,19 +89,19 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
private readonly string _appId; private readonly string _appId;
private readonly string _symmetricAlg; private readonly string _symmetricAlg;
private readonly string _symmetricNum; private readonly string _symmetricNum;
private readonly string _symmetricKey; private readonly string _symmetricEncodingKey;
private readonly string _asymmetricAlg; private readonly string _asymmetricAlg;
private readonly string _asymmetricNum; private readonly string _asymmetricNum;
private readonly string _asymmetricPrivateKey; private readonly string _asymmetricPrivateKey;
private readonly Func<string, bool>? _customRequestPathMatcher; private readonly Func<string, bool>? _customRequestPathMatcher;
public WechatApiSecurityApiInterceptor(string baseUrl, string appId, string symmetricAlg, string symmetricNum, string symmetricKey, string asymmetricAlg, string asymmetricNum, string asymmetricPrivateKey, Func<string, bool>? customRequestPathMatcher) public WechatApiSecurityApiInterceptor(string baseUrl, string appId, string symmetricAlg, string symmetricNum, string symmetricEncodingKey, string asymmetricAlg, string asymmetricNum, string asymmetricPrivateKey, Func<string, bool>? customRequestPathMatcher)
{ {
_baseUrl = baseUrl; _baseUrl = baseUrl;
_appId = appId; _appId = appId;
_symmetricAlg = symmetricAlg; _symmetricAlg = symmetricAlg;
_symmetricNum = symmetricNum; _symmetricNum = symmetricNum;
_symmetricKey = symmetricKey; _symmetricEncodingKey = symmetricEncodingKey;
_asymmetricAlg = asymmetricAlg; _asymmetricAlg = asymmetricAlg;
_asymmetricNum = asymmetricNum; _asymmetricNum = asymmetricNum;
_asymmetricPrivateKey = asymmetricPrivateKey; _asymmetricPrivateKey = asymmetricPrivateKey;
@@ -116,10 +117,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
return; return;
if (context.FlurlCall.HttpRequestMessage.RequestUri is null) if (context.FlurlCall.HttpRequestMessage.RequestUri is null)
return; return;
if (!IsRequestUrlRequireEncryption(context.FlurlCall.HttpRequestMessage.RequestUri)) if (!IsRequestUrlPathMatched(context.FlurlCall.HttpRequestMessage.RequestUri))
return; return;
string urlpath = GetRequestUrl(context.FlurlCall.HttpRequestMessage.RequestUri); string urlpath = GetRequestUrlPath(context.FlurlCall.HttpRequestMessage.RequestUri);
string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString(); string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
string postData = "{}"; string postData = "{}";
string postDataEncrypted; string postDataEncrypted;
@@ -140,7 +141,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
} }
// 对称加密 // 对称加密
if (string.IsNullOrEmpty(_symmetricKey)) if (string.IsNullOrEmpty(_symmetricEncodingKey))
{ {
throw new WechatApiException("Failed to encrypt request, because the AES/SM4 key is not set."); throw new WechatApiException("Failed to encrypt request, because the AES/SM4 key is not set.");
} }
@@ -180,7 +181,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
{ {
const int TAG_LENGTH_BYTE = 16; const int TAG_LENGTH_BYTE = 16;
byte[] cipherBytes = Utilities.AESUtility.EncryptWithGCM( byte[] cipherBytes = Utilities.AESUtility.EncryptWithGCM(
keyBytes: Convert.FromBase64String(_symmetricKey), keyBytes: Convert.FromBase64String(_symmetricEncodingKey),
nonceBytes: Convert.FromBase64String(nonce), nonceBytes: Convert.FromBase64String(nonce),
associatedDataBytes: Encoding.UTF8.GetBytes(associatedData), associatedDataBytes: Encoding.UTF8.GetBytes(associatedData),
plainBytes: Encoding.UTF8.GetBytes(plainData) plainBytes: Encoding.UTF8.GetBytes(plainData)
@@ -207,7 +208,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
{ {
const int TAG_LENGTH_BYTE = 16; const int TAG_LENGTH_BYTE = 16;
byte[] cipherBytes = Utilities.SM4Utility.EncryptWithGCM( byte[] cipherBytes = Utilities.SM4Utility.EncryptWithGCM(
keyBytes: Convert.FromBase64String(_symmetricKey), keyBytes: Convert.FromBase64String(_symmetricEncodingKey),
nonceBytes: Convert.FromBase64String(nonce), nonceBytes: Convert.FromBase64String(nonce),
associatedDataBytes: Encoding.UTF8.GetBytes(associatedData), associatedDataBytes: Encoding.UTF8.GetBytes(associatedData),
plainBytes: Encoding.UTF8.GetBytes(plainData) plainBytes: Encoding.UTF8.GetBytes(plainData)
@@ -250,7 +251,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
} }
else else
{ {
string signData = $"{urlpath}\n{_appId}\n{timestamp}\n{postDataEncrypted}"; string signData = GenerateAymmetricSigningData(urlpath, _appId, timestamp, postDataEncrypted);
string sign; string sign;
switch (_asymmetricAlg) switch (_asymmetricAlg)
@@ -300,12 +301,12 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
return; return;
if (context.FlurlCall.HttpRequestMessage.RequestUri is null) if (context.FlurlCall.HttpRequestMessage.RequestUri is null)
return; return;
if (!IsRequestUrlRequireEncryption(context.FlurlCall.HttpRequestMessage.RequestUri)) if (!IsRequestUrlPathMatched(context.FlurlCall.HttpRequestMessage.RequestUri))
return; return;
if (context.FlurlCall.HttpResponseMessage is null) if (context.FlurlCall.HttpResponseMessage is null)
return; return;
string urlpath = GetRequestUrl(context.FlurlCall.HttpRequestMessage.RequestUri); string urlpath = GetRequestUrlPath(context.FlurlCall.HttpRequestMessage.RequestUri);
byte[] respBytes = Array.Empty<byte>(); byte[] respBytes = Array.Empty<byte>();
if (context.FlurlCall.HttpResponseMessage.Content is not null) if (context.FlurlCall.HttpResponseMessage.Content is not null)
{ {
@@ -319,7 +320,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
} }
// 对称解密 // 对称解密
if (string.IsNullOrEmpty(_symmetricKey)) if (string.IsNullOrEmpty(_symmetricEncodingKey))
{ {
throw new WechatApiException("Failed to decrypt response, because the AES/SM4 key is not set."); throw new WechatApiException("Failed to decrypt response, because the AES/SM4 key is not set.");
} }
@@ -368,7 +369,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
Buffer.BlockCopy(authtagBytes, 0, cipherBytes, encdataBytes.Length, authtagBytes.Length); Buffer.BlockCopy(authtagBytes, 0, cipherBytes, encdataBytes.Length, authtagBytes.Length);
respBytesDecrypted = Utilities.AESUtility.DecryptWithGCM( respBytesDecrypted = Utilities.AESUtility.DecryptWithGCM(
keyBytes: Convert.FromBase64String(_symmetricKey), keyBytes: Convert.FromBase64String(_symmetricEncodingKey),
nonceBytes: Convert.FromBase64String(sIV!), nonceBytes: Convert.FromBase64String(sIV!),
associatedDataBytes: Encoding.UTF8.GetBytes(associatedData), associatedDataBytes: Encoding.UTF8.GetBytes(associatedData),
cipherBytes: cipherBytes cipherBytes: cipherBytes
@@ -392,7 +393,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
Buffer.BlockCopy(authtagBytes, 0, cipherBytes, encdataBytes.Length, authtagBytes.Length); Buffer.BlockCopy(authtagBytes, 0, cipherBytes, encdataBytes.Length, authtagBytes.Length);
respBytesDecrypted = Utilities.SM4Utility.DecryptWithGCM( respBytesDecrypted = Utilities.SM4Utility.DecryptWithGCM(
keyBytes: Convert.FromBase64String(_symmetricKey), keyBytes: Convert.FromBase64String(_symmetricEncodingKey),
nonceBytes: Convert.FromBase64String(sIV!), nonceBytes: Convert.FromBase64String(sIV!),
associatedDataBytes: Encoding.UTF8.GetBytes(associatedData), associatedDataBytes: Encoding.UTF8.GetBytes(associatedData),
cipherBytes: cipherBytes cipherBytes: cipherBytes
@@ -427,14 +428,19 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Interceptors
return $"{urlpath}|{appId}|{timestamp}|{_symmetricNum}"; return $"{urlpath}|{appId}|{timestamp}|{_symmetricNum}";
} }
private string GetRequestUrl(Uri uri) private string GenerateAymmetricSigningData(string urlpath, string appId, string timestamp, string postdata)
{
return $"{urlpath}\n{appId}\n{timestamp}\n{postdata}";
}
private string GetRequestUrlPath(Uri uri)
{ {
return uri.AbsoluteUri.Substring(0, uri.AbsoluteUri.Length - uri.Query.Length); return uri.AbsoluteUri.Substring(0, uri.AbsoluteUri.Length - uri.Query.Length);
} }
private bool IsRequestUrlRequireEncryption(Uri uri) private bool IsRequestUrlPathMatched(Uri uri)
{ {
string absoluteUrl = GetRequestUrl(uri); string absoluteUrl = GetRequestUrlPath(uri);
if (!absoluteUrl.StartsWith(_baseUrl)) if (!absoluteUrl.StartsWith(_baseUrl))
return false; return false;

View File

@@ -45,10 +45,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
{ {
Interceptors.Add(new Interceptors.WechatApiSecurityApiInterceptor( Interceptors.Add(new Interceptors.WechatApiSecurityApiInterceptor(
baseUrl: FlurlClient.BaseUrl, baseUrl: FlurlClient.BaseUrl,
appId: string.IsNullOrEmpty(options.SecurityApiAppId) ? options.AppId : options.SecurityApiAppId, appId: string.IsNullOrEmpty(options.SecurityApiAppId) ? options.AppId : options.SecurityApiAppId!,
symmetricAlg: options.SecurityApiSymmetricAlgorithm!, symmetricAlg: options.SecurityApiSymmetricAlgorithm!,
symmetricNum: options.SecurityApiSymmetricNumber!, symmetricNum: options.SecurityApiSymmetricNumber!,
symmetricKey: options.SecurityApiSymmetricKey!, symmetricEncodingKey: options.SecurityApiSymmetricEncodingKey!,
asymmetricAlg: options.SecurityApiAsymmetricAlgorithm!, asymmetricAlg: options.SecurityApiAsymmetricAlgorithm!,
asymmetricNum: options.SecurityApiAsymmetricNumber!, asymmetricNum: options.SecurityApiAsymmetricNumber!,
asymmetricPrivateKey: options.SecurityApiAsymmetricPrivateKey!, asymmetricPrivateKey: options.SecurityApiAsymmetricPrivateKey!,

View File

@@ -102,9 +102,9 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
public string? SecurityApiSymmetricNumber { get; set; } public string? SecurityApiSymmetricNumber { get; set; }
/// <summary> /// <summary>
/// 获取或设置 API 安全鉴权模式对称加密密钥。 /// 获取或设置 API 安全鉴权模式对称加密密钥(经过 Base64 编码)
/// </summary> /// </summary>
public string? SecurityApiSymmetricKey { get; set; } public string? SecurityApiSymmetricEncodingKey { get; set; }
/// <summary> /// <summary>
/// 获取或设置 API 安全鉴权模式非对称加密算法。 /// 获取或设置 API 安全鉴权模式非对称加密算法。

View File

@@ -20,7 +20,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
AppId = "wxba6223c06417af7b", AppId = "wxba6223c06417af7b",
SecurityApiEnabled = true, SecurityApiEnabled = true,
SecurityApiSymmetricNumber = "fa05fe1e5bcc79b81ad5ad4b58acf787", SecurityApiSymmetricNumber = "fa05fe1e5bcc79b81ad5ad4b58acf787",
SecurityApiSymmetricKey = "otUpngOjU+nVQaWJIC3D/yMLV17RKaP6t4Ot9tbnzLY=", SecurityApiSymmetricEncodingKey = "otUpngOjU+nVQaWJIC3D/yMLV17RKaP6t4Ot9tbnzLY=",
SecurityApiAsymmetricNumber = "97845f6ed842ea860df6fdf65941ff56", SecurityApiAsymmetricNumber = "97845f6ed842ea860df6fdf65941ff56",
SecurityApiAsymmetricPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA3FoQOmOl5/CF5hF7ta4EzCy2LaU3Eu2k9DBwQ73J82I53Sx9\nLAgM1DH3IsYohRRx/BESfbdDI2powvr6QYKVIC+4Yavwg7gzhZRxWWmT1HruEADC\nZAgkUCu+9Il/9FPuitPSoIpBd07NqdkkRe82NBOfrKTdhge/5zd457fl7J81Q5VT\nIxO8vvq7FSw7k6Jtv+eOjR6SZOWbbUO7f9r4UuUkXmvdGv21qiqtaO1EMw4tUCEL\nzY73M7NpCH3RorlommYX3P6q0VrkDHrCE0/QMhmHsF+46E+IRcJ3wtEj3p/mO1Vo\nCpEhawC1U728ZUTwWNEii8hPEhcNAZTKaQMaTQIDAQABAoIBAQCXv5p/a5KcyYKc\n75tfgekh5wTLKIVmDqzT0evuauyCJTouO+4z/ZNAKuzEUO0kwPDCo8s1MpkU8boV\n1Ru1M8WZNePnt65aN+ebbaAl8FRzNvltoeg9VXIUmBvYcjzhOVAE4V2jW7M8A9QU\nzUpyswuED6OeFKfOHtYk2In2IipAqhfbyc6gn7uZSWTQsoO6hGBRQ7Ejx+vgwrbx\nZKVZ7UXbPHD0lOEPraA3PH/QUeUKpNwK2NXQoBxWcR283/HxFSAjjSSsGSBKsCnw\nDN55P2FQ0HNi5YrwUNT9190NIXSeygaRy1b+D+yBfm+yE7/qXwHLZCHsjO+2tMSS\n3KGjllTBAoGBAP9FPeYNKZuu5jt9RpZwXCc9E7Iz7bmM7zws6dun6dQH0xVVWFVm\niGIu07eqyB8HNagXseFzoXLV5EQx+3DaB0bAH+ZEpHGJJpAWSLusigssFUFuTvTF\nw+rC5hxOfidMa6+93SU5pWeJb0zJF8PRDaJ3UmwlwpYubF17sT4PD6p9AoGBANz7\nRlhRSFvggJjhEMpek3OIYWrrlRNO2MVcP7i/fGNTHhrw7OHcNGRof54QZ2Y0baL7\n1vHNokbK2mnT+cQXY/gXMmcE/eV4xyRGYiIL9nBdrkLerc43EYPv+evDvgyji6+y\n4np5cKqHrS8F+YzATk82Jt9HgdI2MvfbJTkSbmgRAoGAHNPL9rPb1An/VA6Ery6H\nKaM7Gy/EE+U3ixsjWbvvqxMrIkieDh7jHftdy2sM6Hwe8hmi6+vr+pTvD0h5tbfZ\nhILj11Q/Idc0NKdflVoZyMM0r0vuvLOsuVFDPUUb+AIoUxNk6vREmpmpqQk4ltN/\n763779yfyef6MuBqFrEKut0CgYB9FfsuuOv1nfINF7EybDCZAETsiee7ozEPHnWv\ndSzK6FytMV1VSBmcEI7UgUKWVu0MifOUsiq+WcsihmvmNLtQzoioSeoSP7ix7ulT\njmP0HQMsNPI7PW67uVZFv2pPqy/Bx8dtPlqpHN3KNV6Z7q0lJ2j/kHGK9UUKidDb\nKnS2kQKBgHZ0cYzwh9YnmfXx9mimF57aQQ8aFc9yaeD5/3G2+a/FZcHtYzUdHQ7P\nPS35blD17/NnhunHhuqakbgarH/LIFMHITCVuGQT4xS34kFVjFVhiT3cHfWyBbJ6\nGbQuzzFxz/UKDDKf3/ON41k8UP20Gdvmv/+c6qQjKPayME81elus\n-----END RSA PRIVATE KEY-----" SecurityApiAsymmetricPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA3FoQOmOl5/CF5hF7ta4EzCy2LaU3Eu2k9DBwQ73J82I53Sx9\nLAgM1DH3IsYohRRx/BESfbdDI2powvr6QYKVIC+4Yavwg7gzhZRxWWmT1HruEADC\nZAgkUCu+9Il/9FPuitPSoIpBd07NqdkkRe82NBOfrKTdhge/5zd457fl7J81Q5VT\nIxO8vvq7FSw7k6Jtv+eOjR6SZOWbbUO7f9r4UuUkXmvdGv21qiqtaO1EMw4tUCEL\nzY73M7NpCH3RorlommYX3P6q0VrkDHrCE0/QMhmHsF+46E+IRcJ3wtEj3p/mO1Vo\nCpEhawC1U728ZUTwWNEii8hPEhcNAZTKaQMaTQIDAQABAoIBAQCXv5p/a5KcyYKc\n75tfgekh5wTLKIVmDqzT0evuauyCJTouO+4z/ZNAKuzEUO0kwPDCo8s1MpkU8boV\n1Ru1M8WZNePnt65aN+ebbaAl8FRzNvltoeg9VXIUmBvYcjzhOVAE4V2jW7M8A9QU\nzUpyswuED6OeFKfOHtYk2In2IipAqhfbyc6gn7uZSWTQsoO6hGBRQ7Ejx+vgwrbx\nZKVZ7UXbPHD0lOEPraA3PH/QUeUKpNwK2NXQoBxWcR283/HxFSAjjSSsGSBKsCnw\nDN55P2FQ0HNi5YrwUNT9190NIXSeygaRy1b+D+yBfm+yE7/qXwHLZCHsjO+2tMSS\n3KGjllTBAoGBAP9FPeYNKZuu5jt9RpZwXCc9E7Iz7bmM7zws6dun6dQH0xVVWFVm\niGIu07eqyB8HNagXseFzoXLV5EQx+3DaB0bAH+ZEpHGJJpAWSLusigssFUFuTvTF\nw+rC5hxOfidMa6+93SU5pWeJb0zJF8PRDaJ3UmwlwpYubF17sT4PD6p9AoGBANz7\nRlhRSFvggJjhEMpek3OIYWrrlRNO2MVcP7i/fGNTHhrw7OHcNGRof54QZ2Y0baL7\n1vHNokbK2mnT+cQXY/gXMmcE/eV4xyRGYiIL9nBdrkLerc43EYPv+evDvgyji6+y\n4np5cKqHrS8F+YzATk82Jt9HgdI2MvfbJTkSbmgRAoGAHNPL9rPb1An/VA6Ery6H\nKaM7Gy/EE+U3ixsjWbvvqxMrIkieDh7jHftdy2sM6Hwe8hmi6+vr+pTvD0h5tbfZ\nhILj11Q/Idc0NKdflVoZyMM0r0vuvLOsuVFDPUUb+AIoUxNk6vREmpmpqQk4ltN/\n763779yfyef6MuBqFrEKut0CgYB9FfsuuOv1nfINF7EybDCZAETsiee7ozEPHnWv\ndSzK6FytMV1VSBmcEI7UgUKWVu0MifOUsiq+WcsihmvmNLtQzoioSeoSP7ix7ulT\njmP0HQMsNPI7PW67uVZFv2pPqy/Bx8dtPlqpHN3KNV6Z7q0lJ2j/kHGK9UUKidDb\nKnS2kQKBgHZ0cYzwh9YnmfXx9mimF57aQQ8aFc9yaeD5/3G2+a/FZcHtYzUdHQ7P\nPS35blD17/NnhunHhuqakbgarH/LIFMHITCVuGQT4xS34kFVjFVhiT3cHfWyBbJ6\nGbQuzzFxz/UKDDKf3/ON41k8UP20Gdvmv/+c6qQjKPayME81elus\n-----END RSA PRIVATE KEY-----"
}) })