You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为多租户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();

额外说明

  • 租户登录入口:若需让用户选择登录租户,可在前端添加对应按钮,指向不同认证方案的挑战路径:
    <a asp-action="Challenge" asp-route-scheme="OpenIdConnect-Tenant-租户1ID">登录租户1</a>
    <a asp-action="Challenge" asp-route-scheme="OpenIdConnect-Tenant-租户2ID">登录租户2</a>
    
    对应的Controller方法:
    public IActionResult Challenge(string scheme)
    {
        return Challenge(new AuthenticationProperties { RedirectUri = "/" }, scheme);
    }
    
  • Token缓存隔离:默认AddInMemoryTokenCaches为所有方案共享内存缓存,若需隔离租户Token,可改用分布式缓存或自定义缓存实现。
  • 权限一致性:确保两个应用注册都已配置下游API(如Microsoft Graph)的对应权限,避免部分租户用户无法访问资源。

内容的提问来源于stack exchange,提问作者StanSm789

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 21:57:32