我的 ASP.NET Core App Service Web API 如何同时支持 AAD Bearer Token 和 Client Certificate Auth?

How can my ASP.NET Core App Service Web API support both AAD Bearer Token and Client Certificate Auth?

我有一个 App Service Web 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 设置为 *

应用服务需要客户端证书设置:

我已经从需要传入证书中排除了所有路径,因为我希望 Aad 或 Cert auth 可用。

备注:

我有什么办法可以:

  1. 获取要通过我的产品应用服务应用中的用户声明传递的客户端证书?
  2. 获取应用服务以通过 X-ARR-ClientCert header 而无需 强制要求存在客户端证书?
  3. 有什么我遗漏的/更好的方法吗?

正如您已经发现的那样,Azure 应用服务不会为排除的路径设置 X-ARR-ClientCert 请求 header,因为 (server-level) 身份验证已被禁用。

禁用Web app client certificates and attach and get the certificate from a custom header using options.CertificateHeader = "value"