Как мой ASP. NET веб-API базовой службы приложений может поддерживать как токен-носитель AAD, так и аутентификацию сертификата клиента? - PullRequest
1 голос
/ 07 мая 2020

У меня есть веб-API службы приложений, который я хочу поддерживать как Azure аутентификацию Active Directory, так и аутентификацию сертификата клиента.

Я следовал этим руководствам, чтобы добраться туда, где я:

Вот настройки, которые у меня есть до сих пор:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddAuthentication()
        .AddAzureADBearer(options => Configuration.Bind("AzureAd", options))
        .AddCertificate();

    services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
    {
        options.TokenValidationParameters.ValidAudiences = new[]
        {
            options.Audience,
        };
    });

    services
        .AddAuthorization(options =>
        {
            options.DefaultPolicy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .AddAuthenticationSchemes(
                    CertificateAuthenticationDefaults.AuthenticationScheme,
                    AzureADDefaults.JwtBearerAuthenticationScheme)
                .Build();
        });

    services.AddSingleton<IAuthorizationHandler, MyAuthorizationHandler>();

    services.AddControllers().AddControllersAsServices();
}

public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseAuthentication();
    app.UseAuthorization();
    ...

MyAuthorizationHandler.cs

public class MyAuthorizationHandler : IAuthorizationHandler
{
    private const string AppIdClaimType = "appid";
    private const string AppIdACRClaimType = "appidacr";

    private readonly HashSet<string> allowedCertificateSubjects;
    private readonly HashSet<string> allowedAadClients;

    private readonly IWebHostEnvironment env;
    private readonly IHttpContextAccessor httpContextAccessor;
    public MyAuthorizationHandler(
        IWebHostEnvironment env,
        IHttpContextAccessor httpContextAccessor,
        IUnityContainer unityContainer)
    {
        this.env = env;
        this.httpContextAccessor = httpContextAccessor;
        allowedCertificateSubjects = // Get from DI;
        allowedAadClients = // Get from DI;
    }

    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        bool isAuthorized = false;

        // Check for Certificate First
        string certificateSubjectName = null;
        if (env.IsDevelopment())
        {
            // Is Local environment, the cert is pasded through the Claims
            Claim subjectNameClaim = context.User.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Name);

            if (subjectNameClaim != null)
            {
                certificateSubjectName = subjectNameClaim.Value;
            }
        }
        else
        {
            // https://docs.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth
            // App Service by default captures the client certificate, and passes it through
            // in the Header X-ARR-ClientCert. We have to read it from there to verify.
            string certHeader = httpContextAccessor.HttpContext.Request.Headers["X-ARR-ClientCert"];
            if (!string.IsNullOrEmpty(certHeader))
            {
                try
                {
                    var certificate = new X509Certificate2(Convert.FromBase64String(certHeader));
                    certificateSubjectName = certificate.GetNameInfo(X509NameType.SimpleName, forIssuer: false);
                }
                catch (Exception)
                {
                    // If there is an error parsing the value (e.g. fake value passed in header),
                    // we should not error, but just ignore the header value.
                }
            }
        }

        // Validate Certificate
        if (allowedCertificateSubjects.Contains(certificateSubjectName, StringComparer.OrdinalIgnoreCase))
        {
            isAuthorized = true;
        }
        else
        {
            // If no cert found or not valid, check for AAD Bearer Token
            Claim authTypeClaim = context.User.Claims.FirstOrDefault(claim => claim.Type == AppIdACRClaimType);
            Claim claimAppId = context.User.Claims.FirstOrDefault(claim => claim.Type == AppIdClaimType);

            if (authTypeClaim != null && claimAppId != null)
            {
                // We only support Client/Secret and Cert AAD auth, not user auth.
                bool isValidAuthType = authTypeClaim.Value == "1" || authTypeClaim.Value == "2";
                bool isValidAppId = allowedAadClients.Contains(claimAppId.Value, StringComparer.OrdinalIgnoreCase);

                if (isValidAuthType && isValidAppId)
                {
                    isAuthorized = true;
                }
            }
        }

        if (!isAuthorized)
        {
            context.Fail();
        }

        return Task.CompletedTask;
    }
}

В настройках приложения WEBSITE_LOAD_CERTIFICATES установлено значение *

Служба приложений требует установки сертификата клиента:

enter image description here

Я исключил все пути из Требовать входящего сертификата, так как я хочу, чтобы были доступны либо Aad, либо Cert auth.

Примечания:

  • При локальном запуске моего API сертификат правильно подбирается и проходит через утверждения. При запуске в моей службе приложений кажется, что сертификат удаляется службой приложений. Вот почему у меня есть этот if (env.IsDevelopment()) оператор, чтобы выбирать между утверждениями и X-ARR-ClientCert заголовком.
  • Когда я исключаю все пути из моих «Входящих клиентских сертификатов», заголовок X-ARR-ClientCert не передается. Когда я удаляю исключение, он правильно передает заголовок.

Есть ли у меня какой-либо способ:

  1. Получить сертификат клиента, который будет передаваться через утверждения пользователей в моем приложении службы приложений продукта?
  2. Получить службу приложений для передачи заголовка X-ARR-ClientCert без указания наличия сертификата клиента?
  3. Что-то мне не хватает / способ лучше сделать?

1 Ответ

1 голос
/ 07 мая 2020

Как вы уже обнаружили, Azure Службы приложений не будут устанавливать заголовок запроса X-ARR-ClientCert для исключенных путей, поскольку для них отключена (на уровне сервера) аутентификация.

Отключите сертификаты клиентов веб-приложений и прикрепите и получите сертификат из настраиваемого заголовка с помощью options.CertificateHeader = "value" .

...