Многопользовательское приложение Azure ASP.Net-Core с авторизацией на предъявителя

Как настроить авторизацию на предъявителя для мультитенантного приложения?

Это одностраничное приложение. В приложении на сайте браузера используйте Adal.js для аутентификации пользователей. После аутентификации приложение отправляет запрос на сервер ASP.Net-Core с заголовком Authorization Bearer.

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 возвращает неавторизованный ответ.

ОБНОВИТЬ

Сообщение Общая конечная точка: ходит как арендатор, разговаривает как арендатор… но не является арендатором, опишите причину проблемы с общими полномочиями.

Вкратце: токен (который отправляется как заголовок носителя авторизации и должен быть проверен на стороне сервера) содержит строку «эмитента», например: https://sts.windows.net/<TENAT_ID>. <TENAT_ID> - будет реальная <TENAT_ID>, а не "обычная" строка.

Итак, когда заголовок носителя авторизации подтвержден, строка «эмитент» сравнивается с настроенными параметрами. Параметр авторизации.

Чтобы решить эту проблему, можно отключить проверку эмитента. И сделай сам:

    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}";
    }

Я не уверен, что это правильный способ проверить эмитента. Пожалуйста, дайте мне знать, правильно это или нет.


person user1167761    schedule 01.05.2018    source источник


Ответы (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();

}

Вы можете обратиться к этому разделу документа Microsoft для справки - Аутентификация в веб-API

person Mohit_Garg    schedule 11.05.2018
comment
Спасибо. tenantManager.FindByIssuerValueAsync() - это настраиваемый метод проверки эмитента по настраиваемой базе данных. Мое приложение не хранит список разрешенных клиентов, поэтому я не могу проверить эмитента по базе данных. Можно ли проверить, что эмитент является точкой авторизации Microsoft, как эта Issuer.StartsWith("https://sts.windows.net/")? - person user1167761; 14.05.2018

В этом посте показано, как выполнить пользовательскую проверку эмитента. 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
    };
}

Когда вы указываете Полномочия для промежуточного программного обеспечения, оно автоматически пытается найти открытые ключи для проверки токена, как описано здесь https://github.com/Azure-Samples/active-directory-javascript-singlepageapp-dotnet-webapi-v2/issues/7

person Dasith Wijes    schedule 10.04.2019
comment
Подробнее о процессе проверки токена devblogs.microsoft.com/aspnet/ - person Dasith Wijes; 10.04.2019