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