ASP.NET Core运行时动态增删服务及AzureAD租户配置问询
好问题!首先得明确一个核心限制:ASP.NET Core的ServiceProvider在构建完成后是不可修改的——你通过services.BuildServiceProvider()创建的服务容器是固定的,没法直接在运行时往里面新增或移除服务。不过针对你要实现的「无需重启系统,动态为租户配置AzureAD认证」的需求,我们完全可以绕开这个限制,用更贴合场景的方案来实现,不需要修改已构建的服务集合。
针对你的AzureAD多租户场景的具体实现
你的核心需求是动态适配不同租户的AzureAD认证配置,没必要硬改服务容器,我们可以通过动态认证选项+自定义认证处理程序提供器来实现,下面是具体步骤:
1. 先注册基础认证框架
在ConfigureServices里先搭好基础的认证服务,不用一开始就把所有租户的认证方案都加上:
public void ConfigureServices(IServiceCollection services) { // 注册基础认证服务,用Cookie保存登录状态 services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = "DynamicAzureAD"; // 自定义的动态挑战方案标识 }) .AddCookie(); // 注册我们需要的动态认证相关服务 services.AddSingleton<IAuthenticationHandlerProvider, DynamicAzureADAuthenticationHandlerProvider>(); services.AddSingleton<IOptionsMonitor<AzureADOptions>, DynamicAzureADOptionsMonitor>(); // 别忘了注册HttpContextAccessor,用来从请求里识别租户 services.AddHttpContextAccessor(); }
2. 实现动态认证选项监视器
这个类会在运行时根据当前请求的租户信息(比如请求头、域名、路由参数),从你的租户存储里实时获取对应的AzureAD配置:
public class DynamicAzureADOptionsMonitor : IOptionsMonitor<AzureADOptions> { private readonly IHttpContextAccessor _httpContextAccessor; public DynamicAzureADOptionsMonitor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public AzureADOptions Get(string name) { // 这里示例从请求头获取租户ID,你可以换成域名解析、路由参数等适合你的方式 var tenantId = _httpContextAccessor.HttpContext.Request.Headers["X-Tenant-Id"].FirstOrDefault(); if (string.IsNullOrEmpty(tenantId)) { throw new InvalidOperationException("请求中未找到租户标识"); } // 从你的租户存储(数据库/配置中心等)获取对应租户的AzureAD配置 var tenant = Tenant.GetById(tenantId); return new AzureADOptions { ClientId = tenant.ClientId, TenantId = tenant.TenantId, Instance = "https://login.microsoftonline.com" }; } // 以下是接口要求的方法,不需要监听配置变更的话可以空实现 public IDisposable OnChange(Action<AzureADOptions, string> listener) => NullDisposable.Instance; public AzureADOptions CurrentValue => Get("DynamicAzureAD"); } // 空的Disposable实现,用于OnChange方法 internal class NullDisposable : IDisposable { public static readonly NullDisposable Instance = new(); public void Dispose() { } }
3. 实现动态认证处理程序提供器
这个类会根据当前请求的租户,动态创建对应的AzureAD认证处理程序,确保每次请求都用正确的租户配置:
public class DynamicAzureADAuthenticationHandlerProvider : IAuthenticationHandlerProvider { private readonly IServiceProvider _serviceProvider; public DynamicAzureADAuthenticationHandlerProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme) { // 只处理我们自定义的动态认证方案 if (authenticationScheme == "DynamicAzureAD") { // 动态创建AzureAD认证处理程序,并完成初始化 var handler = ActivatorUtilities.CreateInstance<AzureADAuthenticationHandler>(_serviceProvider); await handler.InitializeAsync( new AuthenticationScheme("DynamicAzureAD", "动态AzureAD认证", typeof(AzureADAuthenticationHandler)), context); return handler; } // 其他认证方案交给默认的提供器处理 var defaultProvider = _serviceProvider.GetRequiredService<DefaultAuthenticationHandlerProvider>(); return await defaultProvider.GetHandlerAsync(context, authenticationScheme); } }
4. 运行时更新租户配置
当你新增或修改租户的AzureAD配置时,只需要更新你的租户存储(比如数据库、配置中心),完全不需要重启应用——下次请求到来时,DynamicAzureADOptionsMonitor会自动获取最新的配置,实现真正的运行时动态适配。
通用运行时服务增删的思路(如果有其他场景需求)
如果你的需求不只是认证,而是通用的运行时服务增删,可以考虑以下两种方式,但复杂度相对较高:
- 嵌套服务容器:在运行时创建新的
IServiceCollection,添加新服务后构建新的ServiceProvider,用它来获取动态服务。但要注意严格管理容器的生命周期,避免内存泄漏。 - 服务代理模式:注册一个代理类实现目标服务接口,代理类内部根据运行时条件动态切换实际的服务实现。比如注册
IMyService的代理类,代理类根据租户ID选择不同的IMyService实例。
但回到你的场景,动态认证方案的方式是最贴合需求且最稳定的,完全不需要改动原有的服务容器,完美满足你“无需重启系统”的要求。
内容的提问来源于stack exchange,提问作者Linda Lawton - DaImTo

