我的ASP.NET Core App Service Web API如何同时支持AAD承载令牌和客户端证书身份验证?

问题描述 投票:1回答:1

我有一个我想同时支持Azure Active Directory身份验证和客户端证书身份验证的App Service Web API。

我已经按照这些指南到达了我所在的位置:

这里是我到目前为止的设置:

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身份验证可用,因此我已从“要求传入证书”中排除了所有路径。

注意:

  • 在本地运行我的API时,将正确提取证书并通过声明。在我的App Service中运行时,似乎证书已被App Service删除。这就是为什么我在那里有if (env.IsDevelopment())语句在Claims和X-ARR-ClientCert标头之间进行选择的原因。
  • 当我排除“传入客户端证书”中的所有路径时,不会传递X-ARR-ClientCert标头。当我删除排除项时,它会正确传递标题。

我有什么办法可以选择:

  1. 获取要在我的产品应用服务应用中通过用户声明传递的客户证书?
  2. 获取应用服务以传递X-ARR-ClientCert标头,以强制要求提供客户端证书?
  3. 是否有我缺少的东西/做这件事的更好方法?
c# azure asp.net-core azure-active-directory client-certificates
1个回答
0
投票

您已经发现,App服务不会为排除的路径设置X-ARR-ClientCert请求标头,因为已对其禁用(服务器级)身份验证。

禁用webapp cliente certificates并从a custom header using options.CertificateHeader = "value"附加并获取证书。也将证书设置为可选设置ClientCertificateMode to AllowCertificate

© www.soinside.com 2019 - 2024. All rights reserved.