【问题标题】:Azure multi-tenant ASP.Net-Core application with Bearer authorization具有承载授权的 Azure 多租户 ASP.Net-Core 应用程序
【发布时间】:2018-10-11 16:01:30
【问题描述】:

如何为多租户应用设置承载授权?

它是单页应用程序。在浏览器站点应用程序上,使用 Adal.js 对用户进行身份验证。身份验证后,应用程序使用 Authorization Bearer 标头向 ASP.Net-Core 服务器端发送请求。

ASP.Net-Core 使用Microsoft.AspNetCore.Authentication.JwtBearer 来检查请求。这是启动:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));

        // ... other ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseAuthentication();

        // ... other ...
    }
}

这里是 AddAzureAdBearer 方法:

public static class AzureAdServiceCollectionExtensions
{
    public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
        => builder.AddAzureAdBearer(_ => { });
    public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
    {
        builder.Services.Configure(configureOptions);
        builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
        builder.AddJwtBearer();
        return builder;
    }
    private class ConfigureAzureOptions : IConfigureNamedOptions<JwtBearerOptions>
    {
        private readonly AzureAdOptions AzureOptions;
        public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
        {
            AzureOptions = azureOptions.Value;
        }
        public void Configure(string name, JwtBearerOptions options)
        {
            options.Audience = AzureOptions.ClientId;

            // this works (specific TenantId)
            // options.Authority 
            //    = "https://login.microsoftonline.com/f8811864-6950-4347-af1c-9d22bb3d0615"
            // this did not work (common instead of specific TenantId)
            // options.Authority 
            //    = "https://login.microsoftonline.com/common";
            options.Authority = $"{AzureOptions.Instance}{AzureOptions.TenantId}";
        }
        public void Configure(JwtBearerOptions options)
        {
            Configure(Options.DefaultName, options);
        }
    }
 }

对于单租户,这可以按预期工作,可以使用 [Authorize] 属性标记控制器

[Route("api/[controller]")]
[Authorize]
public class CalendarController : Controller
{

对于多租户,我将 Adal.js 设置为公共端点,它正在工作(用户可以成功登录)。但是 ASP.Net-Core 服务器无法检查 Bearer 标头,对于单租户

JwtBearerOptions.Authority = "https://login.microsoftonline.com/f8811864-6950-4347-af1c-9d22bb3d0615"

对于多租户我尝试发送上去

JwtBearerOptions.Authority = "https://login.microsoftonline.com/common"

ASP.Net-Core 服务器返回未经授权的响应。

更新

发帖The Common Endpoint: Walks Like a Tenant, Talks Like a Tenant… But Is Not a Tenant用普通权限描述问题的原因。

简而言之:令牌(作为 Authorization Bearer 标头发送,并且必须在服务器端进行验证)包含“颁发者”字符串,如下所示:https://sts.windows.net/&lt;TENAT_ID&gt;&lt;TENAT_ID&gt; - 将是真实的 &lt;TENAT_ID&gt; 不是“普通”字符串。

因此,当 Authorization Bearer 标头验证后,“issuer”字符串与配置的 options.Authority 设置进行比较。

要解决此问题,可以禁用颁发者验证。自己制作:

    public void Configure(string name, JwtBearerOptions options)
    {
        options.Audience = AzureOptions.ClientId;

        options.TokenValidationParameters = new TokenValidationParameters{
            ValidateIssuer = false
        };
        options.Events = new JwtBearerEvents()
        {
            OnTokenValidated = (context) =>
            {
                if(!context.SecurityToken.Issuer.StartsWith("https://sts.windows.net/"))
                    throw new SecurityTokenValidationException();

                return Task.FromResult(0);
            }
        };

        options.Authority = $"{AzureOptions.Instance}{AzureOptions.TenantId}";
    }

我不确定检查发行人的方法是否正确。请让我知道它是否正确。

【问题讨论】:

    标签: azure azure-active-directory asp.net-core-webapi adal bearer-token


    【解决方案1】:

    这里的这篇文章展示了如何进行自定义颁发者验证。 https://thomaslevesque.com/2018/12/24/multitenant-azure-ad-issuer-validation-in-asp-net-core/

                services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.Authority = "https://login.microsoftonline.com/common";
                    options.Audience = configuration["AzureAdSettings:ClientId"];
                    options.RequireHttpsMetadata = true; // or false if you dont have https
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        IssuerValidator = (issuer, token, parameters) => issuer 
                       //allows any issuer or use `ValidateIssuerWithPlaceholder`
                    };
                });
    

    帖子有这个方法来验证令牌

    private static string ValidateIssuerWithPlaceholder(string issuer, SecurityToken token, TokenValidationParameters parameters)
    {
        // Accepts any issuer of the form "https://login.microsoftonline.com/{tenantid}/v2.0",
        // where tenantid is the tid from the token.
    
        if (token is JwtSecurityToken jwt)
        {
            if (jwt.Payload.TryGetValue("tid", out var value) &&
                value is string tokenTenantId)
            {
                var validIssuers = (parameters.ValidIssuers ?? Enumerable.Empty<string>())
                    .Append(parameters.ValidIssuer)
                    .Where(i => !string.IsNullOrEmpty(i));
    
                if (validIssuers.Any(i => i.Replace("{tenantid}", tokenTenantId) == issuer))
                    return issuer;
            }
        }
    
        // Recreate the exception that is thrown by default
        // when issuer validation fails
        var validIssuer = parameters.ValidIssuer ?? "null";
        var validIssuers = parameters.ValidIssuers == null
            ? "null"
            : !parameters.ValidIssuers.Any()
                ? "empty"
                : string.Join(", ", parameters.ValidIssuers);
        string errorMessage = FormattableString.Invariant(
            $"IDX10205: Issuer validation failed. Issuer: '{issuer}'. Did not match: validationParameters.ValidIssuer: '{validIssuer}' or validationParameters.ValidIssuers: '{validIssuers}'.");
    
        throw new SecurityTokenInvalidIssuerException(errorMessage)
        {
            InvalidIssuer = issuer
        };
    }
    

    当您为中间件指定 Authority 时,它会自动尝试查找公钥来验证令牌,如此处所讨论的https://github.com/Azure-Samples/active-directory-javascript-singlepageapp-dotnet-webapi-v2/issues/7

    【讨论】:

    【解决方案2】:

    是的,你是对的。对于多租户应用程序,将 ValidateIssuer 设置为 false。这意味着应用程序将验证颁发者。

    在 JwtBearerEvents.TokenValidated 事件中验证令牌颁发者。发行者在“iss”声明中发送。

    public override async Task TokenValidated(TokenValidatedContext context)
    {
        var principal = context.Ticket.Principal;
        var tenantManager = context.HttpContext.RequestServices.GetService<TenantManager>();
        var userManager = context.HttpContext.RequestServices.GetService<UserManager>();
        var issuerValue = principal.GetIssuerValue();
        var tenant = await tenantManager.FindByIssuerValueAsync(issuerValue);
    
        if (tenant == null)
        {
            // The caller was not from a trusted issuer. Throw to block the authentication flow.
            throw new SecurityTokenValidationException();
        }
    
        var identity = principal.Identities.First();
    
    }
    

    您可以参考微软文档的这一部分 - Authenticating in the web API

    【讨论】:

    • 谢谢。 tenantManager.FindByIssuerValueAsync() 是一种通过自定义数据库检查发行人的自定义方法。我的应用程序不存储允许的租户列表,因此我无法通过数据库检查颁发者。可以检查发行人是像这样Issuer.StartsWith("https://sts.windows.net/") 的微软授权点吗?
    猜你喜欢
    • 2019-03-11
    • 2020-05-16
    • 1970-01-01
    • 2017-10-10
    • 1970-01-01
    • 1970-01-01
    • 2014-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多