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

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

Это одностраничное приложение. В браузере сайта приложения используйте Adal.js для аутентификации пользователей. После аутентификации приложение отправляет запрос на серверную часть 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 возвращает неавторизованный ответ.

UPDATE

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

Вкратце: токен (который отправляется в качестве заголовка канала авторизации и должен быть проверен на стороне сервера) содержит строку «эмитента», например: 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}";
    }

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

Ответы [ 2 ]

0 голосов
/ 10 апреля 2019

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

0 голосов
/ 11 мая 2018

Да, вы правы. Для многопользовательского приложения установите для 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...