From 675918de08f76cf83fd1b3832e15198e6ecaaa9f Mon Sep 17 00:00:00 2001 From: Aigio Liu Date: Thu, 4 Mar 2021 16:10:19 +0800 Subject: [PATCH] add http2 and skey --- .../Program.cs | 4 +- .../UI/AppHelper.cs | 14 +- .../Models/AppSettings.cs | 23 ++++ .../Models/ApiResponseCode.cs | 2 + .../Services.CloudService/Constants.cs | 13 ++ .../HttpContentCompat.cs | 29 ++++ .../Models/ICloudServiceSettings.cs | 4 +- .../ServiceCollectionExtensions.cs | 6 +- .../Services/CloudService/ApiConnection.cs | 129 ++++++++++++++---- .../CloudService/CloudServiceClientBase.cs | 3 + .../Services/CloudService/IApiConnection.cs | 9 +- .../IApiConnectionPlatformHelper.cs | 3 + ...on.SteamTools.Services.CloudService.csproj | 4 +- System.Common.CoreLib/MediaTypeNames.cs | 2 + 14 files changed, 208 insertions(+), 37 deletions(-) create mode 100644 System.Application.SteamTools.Services.CloudService/HttpContentCompat.cs diff --git a/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs b/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs index 5edf0161d..2ca64db37 100644 --- a/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs +++ b/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs @@ -5,7 +5,7 @@ using System.IO; using System.Properties; using System.Reflection; using System.Threading.Tasks; -using System.Windows; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; [assembly: AssemblyTitle(ThisAssembly.AssemblyTrademark + " v" + ThisAssembly.Version)] namespace System.Application.UI @@ -36,7 +36,7 @@ namespace System.Application.UI } catch (Exception ex) { - AppHelper.SetNLoggerMinLevel(LogLevel.Trace); + AppHelper.TrySetLoggerMinLevel(LogLevel.Trace); // NLog: catch any exception and log it. logger.Error(ex, "Stopped program because of exception"); #if DEBUG diff --git a/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs b/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs index 96555e9fc..c72b1b2cb 100644 --- a/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs +++ b/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs @@ -65,12 +65,24 @@ namespace System.Application.UI static void SetNLoggerMinLevel(LogLevel logLevel) => SetNLoggerMinLevel(ConvertLogLevel(logLevel)); - public static void SetNLoggerMinLevel(NLogLevel logLevel) + static void SetNLoggerMinLevel(NLogLevel logLevel) { NLogManager.GlobalThreshold = logLevel; NInternalLogger.LogLevel = logLevel; } + public static void TrySetLoggerMinLevel(LogLevel logLevel) + { + try + { + LoggerFilterOptions.MinLevel = logLevel; + } + catch + { + } + SetNLoggerMinLevel(ConvertLogLevel(logLevel)); + } + /// /// 日志过滤选项 /// diff --git a/System.Application.SteamTools.Client/Models/AppSettings.cs b/System.Application.SteamTools.Client/Models/AppSettings.cs index 5fa246ce8..753a352fc 100644 --- a/System.Application.SteamTools.Client/Models/AppSettings.cs +++ b/System.Application.SteamTools.Client/Models/AppSettings.cs @@ -52,5 +52,28 @@ namespace System.Application.Models [N_JsonProperty("3")] [S_JsonProperty("3")] public string? AppSecretVisualStudioAppCenter { get; set; } + + [MPKey(4)] + [N_JsonProperty("4")] + [S_JsonProperty("4")] + public string? RSASecret { get; set; } + + RSA? rsa; + + [MPIgnore] + [N_JsonIgnore] + [S_JsonIgnore] + public RSA RSA + { + get + { + if (rsa == null) + { + if (RSASecret == null) throw new ArgumentNullException(nameof(RSASecret)); + rsa = RSAUtils.CreateFromJsonString(RSASecret); + } + return rsa; + } + } } } \ No newline at end of file diff --git a/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs b/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs index af06649f0..ac6407044 100644 --- a/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs +++ b/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs @@ -105,6 +105,8 @@ namespace System.Application.Models EmptyUserAgent, + RequiredSecurityKey, + #endregion } } \ No newline at end of file diff --git a/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs b/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs index 6ebf0c756..6fbf66457 100644 --- a/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs +++ b/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs @@ -10,8 +10,21 @@ namespace System.Application.Services.CloudService public static string NetworkConnectionInterruption => SR.NetworkConnectionInterruption; + public static class Headers + { + public static class Request + { + public const string AppVersion = "App-Version"; + public const string SecurityKey = "App-SKey"; + } + } + public const string HeaderAppVersion = "App-Version"; + public const string HeaderSecurityKey = "App-S-Key"; + + public const string HeaderRspCiphertext = "App-Rsp-Ciphertext"; + /// /// 短信间隔,60秒 /// diff --git a/System.Application.SteamTools.Services.CloudService/HttpContentCompat.cs b/System.Application.SteamTools.Services.CloudService/HttpContentCompat.cs new file mode 100644 index 000000000..051ba2a1f --- /dev/null +++ b/System.Application.SteamTools.Services.CloudService/HttpContentCompat.cs @@ -0,0 +1,29 @@ +#if !NET5_0_OR_GREATER +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace System +{ + public static class HttpContentCompat + { + public static Task ReadAsByteArrayAsync(this HttpContent @this, CancellationToken _) + { + return @this.ReadAsByteArrayAsync(); + } + + public static Task ReadAsStreamAsync(this HttpContent @this, CancellationToken _) + { + return @this.ReadAsStreamAsync(); + } + + public static Task ReadAsStringAsync(this HttpContent @this, CancellationToken _) + { + return @this.ReadAsStringAsync(); + } + } +} + +#endif \ No newline at end of file diff --git a/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs b/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs index 9d4272f41..dd2a51073 100644 --- a/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs +++ b/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs @@ -1,4 +1,4 @@ -using System.Application.Services.CloudService; +using System.Security.Cryptography; namespace System.Application.Models { @@ -7,5 +7,7 @@ namespace System.Application.Models string? ApiBaseUrl { get; set; } Guid AppVersion { get; set; } + + RSA RSA { get; } } } \ No newline at end of file diff --git a/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs b/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs index 8bf92992e..cdb08f528 100644 --- a/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs +++ b/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Application.Services; using System.Application.Services.CloudService; +using System.Net; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection @@ -23,7 +24,10 @@ namespace Microsoft.Extensions.DependencyInjection var sc = s.GetRequiredService(); c.BaseAddress = new Uri(sc.ApiBaseUrl); c.DefaultRequestHeaders.UserAgent.ParseAdd(sc.UserAgent); - c.DefaultRequestHeaders.Add(Constants.HeaderAppVersion, sc.Settings.AppVersion.ToStringN()); + c.DefaultRequestHeaders.Add(Constants.Headers.Request.AppVersion, sc.Settings.AppVersion.ToStringN()); +#if NET5_0_OR_GREATER + c.DefaultRequestVersion = HttpVersion.Version20; +#endif }); services.TryAddSingleton(); services.TryAddSingleton(s => s.GetRequiredService()); diff --git a/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs b/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs index c20397dd5..88ca3f167 100644 --- a/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs +++ b/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs @@ -10,6 +10,7 @@ using System.IO.FileFormats; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -23,7 +24,7 @@ namespace System.Application.Services.CloudService readonly ILogger logger; readonly IHttpPlatformHelper http_helper; readonly IApiConnectionPlatformHelper conn_helper; - readonly Lazy jsonSerializer = new Lazy(() => new JsonSerializer()); + readonly Lazy jsonSerializer = new(() => new JsonSerializer()); readonly IModelValidator validator; public ApiConnection( @@ -86,11 +87,15 @@ namespace System.Application.Services.CloudService /// 请求模型 /// HttpContent? GetRequestContent( - Serializable.ImplType serializableImplType, - TRequestModel? request, - CancellationToken cancellationToken) + bool isSecurity, + Aes? aes, + Serializable.ImplType serializableImplType, + TRequestModel? request, + CancellationToken cancellationToken) { if (request == null) return null; + if (isSecurity && aes == null) throw new ArgumentNullException(nameof(aes)); + HttpContent? httpContent; if (request is IUploadFileSource uploadFile) // 上传单个文件 { @@ -102,6 +107,10 @@ namespace System.Application.Services.CloudService } else { + if (isSecurity && serializableImplType != Serializable.ImplType.MessagePack) + { + serializableImplType = Serializable.ImplType.MessagePack; + } switch (serializableImplType) // 序列化模型 { case Serializable.ImplType.NewtonsoftJson: @@ -110,6 +119,10 @@ namespace System.Application.Services.CloudService case Serializable.ImplType.MessagePack: var byteArray = Serializable.SMP(request, cancellationToken); if (byteArray == null) return null; + if (isSecurity) + { + byteArray = aes.ThrowIsNull(nameof(aes)).Encrypt(byteArray); + } httpContent = new ByteArrayContent(byteArray); httpContent.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.MessagePack); break; @@ -343,7 +356,7 @@ namespace System.Application.Services.CloudService { get { - if (DI.Platform == Platform.Windows || DI.Platform == Platform.Linux || DI.Platform == Platform.Unknown) return true; + if (DI.Platform == Platform.Windows || (DI.Platform == Platform.Apple && DI.DeviceIdiom == DeviceIdiom.Desktop) || DI.Platform == Platform.Linux || DI.Platform == Platform.Unknown) return true; return Connectivity.NetworkAccess == NetworkAccess.Internet; } } @@ -375,7 +388,8 @@ namespace System.Application.Services.CloudService HttpMethod method, string requestUri, TRequestModel? requestModel, - bool responseContentMaybeNull) + bool responseContentMaybeNull, + bool isSecurity) { #region ModelValidator @@ -396,15 +410,56 @@ namespace System.Application.Services.CloudService } IApiResponse responseResult; + + Aes? aes = null; + try { + if (isSecurity) + { + // 行业标准加密 + aes = AESUtils.Create(); + } + + var serializableImplType = Serializable.ImplType.MessagePack; + using var request = new HttpRequestMessage(method, requestUri) { Content = GetRequestContent( - Serializable.ImplType.MessagePack, + isSecurity, + aes, + serializableImplType, requestModel, cancellationToken), }; + + switch (serializableImplType) + { + case Serializable.ImplType.NewtonsoftJson: + case Serializable.ImplType.SystemTextJson: + request.Headers.Accept.ParseAdd(MediaTypeNames.JSON); + break; + case Serializable.ImplType.MessagePack: + if (isSecurity) + { + request.Headers.Accept.ParseAdd(MediaTypeNames.Security); + } + else + { + request.Headers.Accept.ParseAdd(MediaTypeNames.MessagePack); + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(serializableImplType), serializableImplType, null); + } + + if (isSecurity) + { + var skey_bytes = aes.ThrowIsNull(nameof(aes)).ToParamsByteArray(); + var skey_str = conn_helper.RSA.EncryptToString(skey_bytes); + request.Headers.Add(Constants.Headers.Request.SecurityKey, skey_str); + } + await SetRequestHeaderAuthorization(request); var client = conn_helper.CreateClient(); using var response = await client.SendAsync(request, @@ -420,37 +475,47 @@ namespace System.Application.Services.CloudService { if (!isApi && typeof(TResponseModel) == typeof(byte[])) { - var bytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); responseResult = ApiResponse.Code(code, content: (TResponseModel)(object)bytes); } else if (!isApi && typeof(TResponseModel) == typeof(string)) { - var str = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var str = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); responseResult = ApiResponse.Code(code, content: (TResponseModel)(object)str); } else { - var mime = response.Content.Headers.ContentType.MediaType; + var rspIsCiphertext = false; + + var mime = response.Content.Headers.ContentType?.MediaType; + + if (mime == MediaTypeNames.Security) + { + mime = MediaTypeNames.MessagePack; + rspIsCiphertext = true; + } + switch (mime) { case MediaTypeNames.JSON: { - using var stream = await response.Content - .ReadAsStreamAsync().ConfigureAwait(false); + if (rspIsCiphertext) + { + throw new NotSupportedException("At present, JSON does not implement security on the server side, so this cannot happen."); + } + using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream, Encoding.UTF8); using var json = new JsonTextReader(reader); - responseResult = ApiResponse - .Deserialize(jsonSerializer.Value, json); + responseResult = ApiResponse.Deserialize(jsonSerializer.Value, json); } break; case MediaTypeNames.MessagePack: { - using var stream = await response.Content - .ReadAsStreamAsync().ConfigureAwait(false); - responseResult = await ApiResponse - .DeserializeAsync(stream, cancellationToken); + using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + using CryptoStream? cryptoStream = rspIsCiphertext ? new CryptoStream(stream, aes.ThrowIsNull(nameof(aes)).CreateDecryptor(), CryptoStreamMode.Read) : null; + responseResult = await ApiResponse.DeserializeAsync(rspIsCiphertext ? cryptoStream.ThrowIsNull(nameof(cryptoStream)) : stream, cancellationToken); } break; default: @@ -465,6 +530,10 @@ namespace System.Application.Services.CloudService logger.LogError(ex, nameof(ApiConnection)); responseResult = ApiResponse.Code(GetCodeByException(ex)); } + finally + { + aes?.Dispose(); + } await GlobalResponseIntercept( isApi, method, @@ -564,7 +633,7 @@ namespace System.Application.Services.CloudService return responseResult; } - public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request, bool responseContentMaybeNull = false) + public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request, bool responseContentMaybeNull, bool isSecurity) { var rsp = await SendCoreAsync( isApi: true, @@ -572,7 +641,8 @@ namespace System.Application.Services.CloudService method, requestUri, requestModel: request, - responseContentMaybeNull); + responseContentMaybeNull, + isSecurity); return rsp; } @@ -584,7 +654,8 @@ namespace System.Application.Services.CloudService HttpMethod.Get, requestUri, requestModel: null, - responseContentMaybeNull: false); + responseContentMaybeNull: false, + isSecurity: false); return rsp; } @@ -596,11 +667,12 @@ namespace System.Application.Services.CloudService HttpMethod.Get, requestUri, requestModel: null, - responseContentMaybeNull: false); + responseContentMaybeNull: false, + isSecurity: false); return rsp; } - public async Task SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request) + public async Task SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request, bool isSecurity) { var rsp = await SendCoreAsync( isApi: true, @@ -608,7 +680,8 @@ namespace System.Application.Services.CloudService method, requestUri, requestModel: request, - responseContentMaybeNull: true); + responseContentMaybeNull: true, + isSecurity); return rsp; } @@ -620,11 +693,12 @@ namespace System.Application.Services.CloudService method, requestUri, requestModel: null, - responseContentMaybeNull: true); + responseContentMaybeNull: true, + isSecurity: false); return rsp; } - public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, bool responseContentMaybeNull = false) + public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, bool responseContentMaybeNull, bool isSecurity) { var rsp = await SendCoreAsync( isApi: true, @@ -632,7 +706,8 @@ namespace System.Application.Services.CloudService method, requestUri, requestModel: null, - responseContentMaybeNull); + responseContentMaybeNull, + isSecurity); return rsp; } } diff --git a/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs b/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs index 45692c9a6..158288e66 100644 --- a/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs +++ b/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs @@ -5,6 +5,7 @@ using System.Application.Models; using System.Application.Services.CloudService.Clients; using System.Application.Services.CloudService.Clients.Abstractions; using System.Net.Http; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -31,6 +32,8 @@ namespace System.Application.Services.CloudService internal ICloudServiceSettings Settings => settings; + RSA IApiConnectionPlatformHelper.RSA => Settings.RSA; + protected sealed override string? ClientName => ClientName_; public CloudServiceClientBase( diff --git a/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnection.cs b/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnection.cs index c11eaf03c..bb104ad77 100644 --- a/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnection.cs +++ b/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnection.cs @@ -52,7 +52,8 @@ namespace System.Application.Services.CloudService HttpMethod method, string requestUri, TRequestModel? request, - bool responseContentMaybeNull = false); + bool responseContentMaybeNull = false, + bool isSecurity = false); /// /// RequestModel(调用服务端接口) @@ -67,7 +68,8 @@ namespace System.Application.Services.CloudService CancellationToken cancellationToken, HttpMethod method, string requestUri, - TRequestModel? request); + TRequestModel? request, + bool isSecurity = false); /// /// NoModel(调用服务端接口) @@ -93,7 +95,8 @@ namespace System.Application.Services.CloudService CancellationToken cancellationToken, HttpMethod method, string requestUri, - bool responseContentMaybeNull = false); + bool responseContentMaybeNull = false, + bool isSecurity = false); #endregion } diff --git a/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnectionPlatformHelper.cs b/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnectionPlatformHelper.cs index b6242d930..1807423f7 100644 --- a/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnectionPlatformHelper.cs +++ b/System.Application.SteamTools.Services.CloudService/Services/CloudService/IApiConnectionPlatformHelper.cs @@ -1,6 +1,7 @@ using System.Application.Columns; using System.Application.Models; using System.Net.Http; +using System.Security.Cryptography; using System.Threading.Tasks; namespace System.Application.Services.CloudService @@ -33,5 +34,7 @@ namespace System.Application.Services.CloudService void ShowResponseErrorMessage(string message); HttpClient CreateClient(); + + RSA RSA { get; } } } \ No newline at end of file diff --git a/System.Application.SteamTools.Services.CloudService/System.Application.SteamTools.Services.CloudService.csproj b/System.Application.SteamTools.Services.CloudService/System.Application.SteamTools.Services.CloudService.csproj index e018ee975..d2b42aa7d 100644 --- a/System.Application.SteamTools.Services.CloudService/System.Application.SteamTools.Services.CloudService.csproj +++ b/System.Application.SteamTools.Services.CloudService/System.Application.SteamTools.Services.CloudService.csproj @@ -1,7 +1,7 @@ - + - netstandard2.1 + netstandard2.1;net5.0 System.Application diff --git a/System.Common.CoreLib/MediaTypeNames.cs b/System.Common.CoreLib/MediaTypeNames.cs index 29a821d58..a20b91c0d 100644 --- a/System.Common.CoreLib/MediaTypeNames.cs +++ b/System.Common.CoreLib/MediaTypeNames.cs @@ -54,5 +54,7 @@ public const string XML = "text/xml"; public const string XML_APP = "application/xml"; + + public const string Security = "application/vnd.sapi+x-msgpack"; } } \ No newline at end of file