From ad3cc3ebb684152918e0055fa7735cc18bcedbbe Mon Sep 17 00:00:00 2001 From: Diego <2248356998@qq.com> Date: Mon, 29 Jul 2024 09:47:58 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=B7=BB=E5=8A=A0Cors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...orsAccessorApplicationBuilderExtensions.cs | 49 +++++++++++ ...CorsAccessorServiceCollectionExtensions.cs | 60 ++++++++++++++ .../CorsAccessor/Internal/Penetrates.cs | 82 +++++++++++++++++++ .../Options/CorsAccessorSettingsOptions.cs | 72 ++++++++++++++++ src/ThingsGateway.Server/Program.cs | 12 +-- src/ThingsGateway.Server/SingleFilePublish.cs | 1 - src/ThingsGateway.Server/appsettings.json | 7 ++ 7 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorApplicationBuilderExtensions.cs create mode 100644 src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorServiceCollectionExtensions.cs create mode 100644 src/ThingsGateway.Server/Extensions/CorsAccessor/Internal/Penetrates.cs create mode 100644 src/ThingsGateway.Server/Extensions/CorsAccessor/Options/CorsAccessorSettingsOptions.cs diff --git a/src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorApplicationBuilderExtensions.cs b/src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorApplicationBuilderExtensions.cs new file mode 100644 index 000000000..89b1f9cb2 --- /dev/null +++ b/src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorApplicationBuilderExtensions.cs @@ -0,0 +1,49 @@ +// 版权归百小僧及百签科技(广东)有限公司所有。 + +using Furion.CorsAccessor; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Builder; + +/// +/// 跨域中间件拓展 +/// +public static class CorsAccessorApplicationBuilderExtensions +{ + /// + /// 添加跨域中间件 + /// + /// + /// + /// + public static IApplicationBuilder UseCorsAccessor(this IApplicationBuilder app, Action corsPolicyBuilderHandler = default) + { + // 获取选项 + var corsAccessorSettings = app.ApplicationServices.GetService>().Value; + + // 判断是否启用 SignalR 跨域支持 + if (corsAccessorSettings.SignalRSupport == false) + { + // 配置跨域中间件 + _ = corsPolicyBuilderHandler == null + ? app.UseCors(corsAccessorSettings.PolicyName) + : app.UseCors(corsPolicyBuilderHandler); + } + else + { + // 配置跨域中间件 + app.UseCors(builder => + { + // 设置跨域策略 + Penetrates.SetCorsPolicy(builder, corsAccessorSettings, true); + + // 添加自定义配置 + corsPolicyBuilderHandler?.Invoke(builder); + }); + } + + return app; + } +} diff --git a/src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorServiceCollectionExtensions.cs b/src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorServiceCollectionExtensions.cs new file mode 100644 index 000000000..0a6cf90fe --- /dev/null +++ b/src/ThingsGateway.Server/Extensions/CorsAccessor/Extensions/CorsAccessorServiceCollectionExtensions.cs @@ -0,0 +1,60 @@ +// 版权归百小僧及百签科技(广东)有限公司所有。 + +using Furion; +using Furion.CorsAccessor; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +using ThingsGateway; +using ThingsGateway.Core; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// 跨域访问服务拓展类 +/// +public static class CorsAccessorServiceCollectionExtensions +{ + /// + /// 配置跨域 + /// + /// 服务集合 + /// + /// + /// 服务集合 + public static IServiceCollection AddCorsAccessor(this IServiceCollection services, Action corsOptionsHandler = default, Action corsPolicyBuilderHandler = default) + { + // 解决服务重复注册问题 + if (services.Any(u => u.ServiceType == typeof(IConfigureOptions))) + { + return services; + } + + // 添加跨域配置选项 + services.TryAddSingleton, Microsoft.Extensions.DependencyInjection.ConfigureOptions>(); + + // 获取选项 + var corsAccessorSettings = App.Configuration.GetSection("CorsAccessorSettings").Get(); + + // 添加跨域服务 + services.AddCors(options => + { + // 添加策略跨域 + options.AddPolicy(corsAccessorSettings.PolicyName, builder => + { + // 设置跨域策略 + Penetrates.SetCorsPolicy(builder, corsAccessorSettings); + + // 添加自定义配置 + corsPolicyBuilderHandler?.Invoke(builder); + }); + + // 添加自定义配置 + corsOptionsHandler?.Invoke(options); + }); + + return services; + } +} diff --git a/src/ThingsGateway.Server/Extensions/CorsAccessor/Internal/Penetrates.cs b/src/ThingsGateway.Server/Extensions/CorsAccessor/Internal/Penetrates.cs new file mode 100644 index 000000000..4247b10ae --- /dev/null +++ b/src/ThingsGateway.Server/Extensions/CorsAccessor/Internal/Penetrates.cs @@ -0,0 +1,82 @@ +// 版权归百小僧及百签科技(广东)有限公司所有。 + +using Microsoft.AspNetCore.Cors.Infrastructure; + +namespace Furion.CorsAccessor; + +/// +/// 常量、公共方法配置类 +/// +internal static class Penetrates +{ + /// + /// 默认跨域导出响应头 Key + /// + /// 解决 ajax,XMLHttpRequest,axios 不能获取请求头问题 + private static readonly string[] _defaultExposedHeaders = new[] + { + "access-token", + "x-access-token", + "Content-Disposition" + }; + + /// + /// 设置跨域策略 + /// + /// + /// + /// + internal static void SetCorsPolicy(CorsPolicyBuilder builder, CorsAccessorSettingsOptions corsAccessorSettings, bool isMiddleware = false) + { + // 判断是否设置了来源,因为 AllowAnyOrigin 不能和 AllowCredentials一起公用 + var isNotSetOrigins = corsAccessorSettings.WithOrigins == null || corsAccessorSettings.WithOrigins.Length == 0; + + // https://docs.microsoft.com/zh-cn/aspnet/core/signalr/security?view=aspnetcore-6.0 + var isSupportSignarlR = isMiddleware && corsAccessorSettings.SignalRSupport == true; + + // 设置总是允许跨域源配置 + builder.SetIsOriginAllowed(_ => true); + + // 如果没有配置来源,则允许所有来源 + if (isNotSetOrigins) + { + // 解决 SignarlR 不能配置允许所有源问题 + if (!isSupportSignarlR) builder.AllowAnyOrigin(); + } + else builder.WithOrigins(corsAccessorSettings.WithOrigins) + .SetIsOriginAllowedToAllowWildcardSubdomains(); + + // 如果没有配置请求标头,则允许所有表头,包含处理 SignarlR 情况 + if ((corsAccessorSettings.WithHeaders == null || corsAccessorSettings.WithHeaders.Length == 0) || isSupportSignarlR) builder.AllowAnyHeader(); + else builder.WithHeaders(corsAccessorSettings.WithHeaders); + + // 如果没有配置任何请求谓词,则允许所有请求谓词 + if (corsAccessorSettings.WithMethods == null || corsAccessorSettings.WithMethods.Length == 0) builder.AllowAnyMethod(); + else + { + // 解决 SignarlR 必须允许 GET POST 问题 + if (isSupportSignarlR) + { + builder.WithMethods(corsAccessorSettings.WithMethods.Concat(new[] { "GET", "POST" }).Distinct(StringComparer.OrdinalIgnoreCase).ToArray()); + } + else builder.WithMethods(corsAccessorSettings.WithMethods); + } + + // 配置跨域凭据,包含处理 SignarlR 情况 + if ((corsAccessorSettings.AllowCredentials == true && !isNotSetOrigins) || isSupportSignarlR) builder.AllowCredentials(); + + // 配置响应头,如果前端不能获取自定义的 header 信息,必须配置该项,默认配置了 access-token 和 x-access-token,可取消默认行为 + IEnumerable exposedHeaders = corsAccessorSettings.FixedClientToken == true + ? _defaultExposedHeaders + : Array.Empty(); + if (corsAccessorSettings.WithExposedHeaders != null && corsAccessorSettings.WithExposedHeaders.Length > 0) + { + exposedHeaders = exposedHeaders.Concat(corsAccessorSettings.WithExposedHeaders).Distinct(StringComparer.OrdinalIgnoreCase); + } + + if (exposedHeaders.Any()) builder.WithExposedHeaders(exposedHeaders.ToArray()); + + // 设置预检过期时间,如果不设置默认为 24小时 + builder.SetPreflightMaxAge(TimeSpan.FromSeconds(corsAccessorSettings.SetPreflightMaxAge ?? 24 * 60 * 60)); + } +} diff --git a/src/ThingsGateway.Server/Extensions/CorsAccessor/Options/CorsAccessorSettingsOptions.cs b/src/ThingsGateway.Server/Extensions/CorsAccessor/Options/CorsAccessorSettingsOptions.cs new file mode 100644 index 000000000..98100b2f7 --- /dev/null +++ b/src/ThingsGateway.Server/Extensions/CorsAccessor/Options/CorsAccessorSettingsOptions.cs @@ -0,0 +1,72 @@ +// 版权归百小僧及百签科技(广东)有限公司所有。 + +using Microsoft.Extensions.Configuration; +using System.ComponentModel.DataAnnotations; + +namespace Furion.CorsAccessor; + +/// +/// 跨域配置选项 +/// +public sealed class CorsAccessorSettingsOptions +{ + /// + /// 策略名称 + /// + [Required] + public string PolicyName { get; set; } + + /// + /// 允许来源域名,没有配置则允许所有来源 + /// + public string[] WithOrigins { get; set; } + + /// + /// 请求表头,没有配置则允许所有表头 + /// + public string[] WithHeaders { get; set; } + + /// + /// 设置客户端可获取的响应标头 + /// + public string[] WithExposedHeaders { get; set; } + + /// + /// 设置跨域允许请求谓词,没有配置则允许所有 + /// + public string[] WithMethods { get; set; } + + /// + /// 是否允许跨域请求中的凭据 + /// + public bool? AllowCredentials { get; set; } + + /// + /// 设置预检过期时间 + /// + public int? SetPreflightMaxAge { get; set; } + + /// + /// 修正前端无法获取 Token 问题 + /// + public bool? FixedClientToken { get; set; } + + /// + /// 启用 SignalR 跨域支持 + /// + public bool? SignalRSupport { get; set; } + + /// + /// 后期配置 + /// + /// + /// + public void PostConfigure(CorsAccessorSettingsOptions options, IConfiguration configuration) + { + PolicyName ??= "App.Cors.Policy"; + WithOrigins ??= Array.Empty(); + AllowCredentials ??= true; + FixedClientToken ??= true; + SignalRSupport ??= false; + } +} diff --git a/src/ThingsGateway.Server/Program.cs b/src/ThingsGateway.Server/Program.cs index 48f4a0619..f7e15fb62 100644 --- a/src/ThingsGateway.Server/Program.cs +++ b/src/ThingsGateway.Server/Program.cs @@ -82,7 +82,7 @@ public class Program }); } - builder.Services.AddCors(); + builder.Services.AddCorsAccessor(); builder.WebHost.UseWebRoot("wwwroot"); //builder.WebHost.UseStaticWebAssets(); @@ -193,15 +193,7 @@ public class Program app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider }); - app.UseStaticFiles(); - var cors = app.Configuration["AllowOrigins"]?.Split(',', StringSplitOptions.RemoveEmptyEntries); - if (cors?.Length > 0) - { - app.UseCors(builder => builder.WithOrigins() - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials()); - } + app.UseCorsAccessor(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/src/ThingsGateway.Server/SingleFilePublish.cs b/src/ThingsGateway.Server/SingleFilePublish.cs index 1b3f67ced..8eb07fb8e 100644 --- a/src/ThingsGateway.Server/SingleFilePublish.cs +++ b/src/ThingsGateway.Server/SingleFilePublish.cs @@ -26,7 +26,6 @@ public class SingleFilePublish : ISingleFilePublish /// public Assembly[] IncludeAssemblies() { - // 需要 Furion 框架扫描哪些程序集就写上去即可 return Array.Empty(); } diff --git a/src/ThingsGateway.Server/appsettings.json b/src/ThingsGateway.Server/appsettings.json index a42ba48b8..f7a07911e 100644 --- a/src/ThingsGateway.Server/appsettings.json +++ b/src/ThingsGateway.Server/appsettings.json @@ -2,6 +2,13 @@ "urls": "http://*:5000", "ConfigurationScanDirectories": [ "" ], + "IgnoreConfigurationFiles": [ "" ], + "CorsAccessorSettings": { + "WithExposedHeaders": [ "access-token", "x-access-token", "environment" ], + "SignalRSupport": true + }, + + //冗余配置 "ManagementOptions": { "PrimaryUri": "127.0.0.1:7101", //主(备)站IP