如何为多租户ASP.NET Core MVC应用配置多应用注册实现租户专属认证
ASP.NET Core MVC 配置多应用注册支持双租户认证(基于Microsoft Identity Platform)
问题描述
我有一个使用Microsoft Identity Platform进行认证的ASP.NET Core MVC应用,当前在Azure中配置为多租户应用注册,可基于租户ID过滤访问,单应用注册时运行正常。现需扩展功能以支持两个不同的应用注册(对应两个独立租户),允许两个租户的用户进行认证。
当前Program.cs相关代码:
string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '); // Sign-in users with the Microsoft identity platform services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(options => { Configuration.Bind("AzureAd", options); options.Events.OnTokenValidated = async context => { string tenantId = context.SecurityToken.Claims.FirstOrDefault(x => x.Type == "tid" || x.Type == "http://schemas.microsoft.com/identity/claims/tenantid")?.Value; var allowedTenants = Configuration.GetSection("AllowedTenants").Get<string[]>().ToList(); if (string.IsNullOrWhiteSpace(tenantId) || !allowedTenants.Contains(tenantId)) throw new UnauthorizedAccessException("Unable to get tenantId from token or the tenant is not authorized."); }; }) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) .AddInMemoryTokenCaches();
当前appsettings.json配置:
"AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "******.onmicrosoft.com", "TenantId": "**********", "ClientId": "**********", "CallbackPath": "/signin-oidc", "SignedOutCallbackPath": "/signout-callback-oidc", "ClientCapabilities": [ "cp1" ], "ClientSecret": "**********" }, "AllowedTenants": [ "**********", "**********" ], "DownstreamApi": { "BaseUrl": "https://graph.microsoft.com/v1.0", "Scopes": "User.Read" }
关键需求
- 如何在
appsettings.json中配置多应用注册? Program.cs中需做哪些修改以处理两个租户的认证,同时保留租户过滤功能?
附加信息:使用Microsoft Identity Web for ASP.NET Core MVC;应用当前为Azure中的多租户应用;基于token中的tenantId声明过滤访问。
解决方案
1. appsettings.json 多应用注册配置
将原有的单个AzureAd节点改为数组形式,命名为AzureAdTenants,每个元素对应一个应用注册的完整配置。必须为每个应用注册设置唯一的CallbackPath,避免登录回调冲突:
"AzureAdTenants": [ { "Instance": "https://login.microsoftonline.com/", "TenantId": "租户1的ID", "ClientId": "应用注册1的ClientID", "CallbackPath": "/signin-oidc-tenant1", "SignedOutCallbackPath": "/signout-callback-oidc-tenant1", "ClientCapabilities": [ "cp1" ], "ClientSecret": "应用注册1的ClientSecret" }, { "Instance": "https://login.microsoftonline.com/", "TenantId": "租户2的ID", "ClientId": "应用注册2的ClientID", "CallbackPath": "/signin-oidc-tenant2", "SignedOutCallbackPath": "/signout-callback-oidc-tenant2", "ClientCapabilities": [ "cp1" ], "ClientSecret": "应用注册2的ClientSecret" } ], "AllowedTenants": [ "租户1的ID", "租户2的ID" ], "DownstreamApi": { "BaseUrl": "https://graph.microsoft.com/v1.0", "Scopes": "User.Read" }
2. Program.cs 代码修改
为每个应用注册添加独立的认证方案,复用租户过滤逻辑,并配置对应的Token获取和Graph访问能力:
string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '); var allowedTenants = Configuration.GetSection("AllowedTenants").Get<string[]>().ToList(); // 初始化认证构建器 var authBuilder = services.AddAuthentication(); // 读取所有应用注册配置,逐个添加认证方案 var azureAdTenants = Configuration.GetSection("AzureAdTenants").Get<List<MicrosoftIdentityOptions>>(); foreach (var tenantConfig in azureAdTenants) { // 为每个租户生成唯一的认证方案名称 string schemeName = $"OpenIdConnect-Tenant-{tenantConfig.TenantId}"; authBuilder.AddMicrosoftIdentityWebApp(options => { // 绑定当前租户的应用注册配置 options.Instance = tenantConfig.Instance; options.TenantId = tenantConfig.TenantId; options.ClientId = tenantConfig.ClientId; options.CallbackPath = tenantConfig.CallbackPath; options.SignedOutCallbackPath = tenantConfig.SignedOutCallbackPath; options.ClientCapabilities = tenantConfig.ClientCapabilities; options.ClientSecret = tenantConfig.ClientSecret; // 保留租户过滤逻辑 options.Events.OnTokenValidated = async context => { string tenantId = context.SecurityToken.Claims.FirstOrDefault(x => x.Type == "tid" || x.Type == "http://schemas.microsoft.com/identity/claims/tenantid")?.Value; if (string.IsNullOrWhiteSpace(tenantId) || !allowedTenants.Contains(tenantId)) throw new UnauthorizedAccessException("无法从Token获取租户ID或当前租户未被授权。"); }; }, schemeName) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) .AddInMemoryTokenCaches(); } // 设置默认认证与挑战方案(可选,指定默认跳转的登录租户) services.Configure<AuthenticationOptions>(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = $"OpenIdConnect-Tenant-{azureAdTenants[0].TenantId}"; }); // 显式添加Cookie认证(Microsoft Identity Web会自动添加,显式声明更清晰) services.AddCookie();
额外说明
- 租户登录入口:若需让用户选择登录租户,可在前端添加对应按钮,指向不同认证方案的挑战路径:
对应的Controller方法:<a asp-action="Challenge" asp-route-scheme="OpenIdConnect-Tenant-租户1ID">登录租户1</a> <a asp-action="Challenge" asp-route-scheme="OpenIdConnect-Tenant-租户2ID">登录租户2</a>public IActionResult Challenge(string scheme) { return Challenge(new AuthenticationProperties { RedirectUri = "/" }, scheme); } - Token缓存隔离:默认
AddInMemoryTokenCaches为所有方案共享内存缓存,若需隔离租户Token,可改用分布式缓存或自定义缓存实现。 - 权限一致性:确保两个应用注册都已配置下游API(如Microsoft Graph)的对应权限,避免部分租户用户无法访问资源。
内容的提问来源于stack exchange,提问作者StanSm789
相关产品推荐
相关产品推荐

