【问题标题】:How can my ASP.NET Core App Service Web API support both AAD Bearer Token and Client Certificate Auth?我的 ASP.NET Core App Service Web API 如何同时支持 AAD Bearer Token 和 Client Certificate Auth?
【发布时间】:2020-08-22 01:30:34
【问题描述】:

我有一个应用服务 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 身份验证可用。

注意事项:

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

我有什么办法:

  1. 获取客户端证书以通过我的 Product App Service 应用程序中的用户声明传递?
  2. 让应用服务传递X-ARR-ClientCert 标头而不强制要求存在客户端证书?
  3. 我有什么遗漏/更好的方法吗?

【问题讨论】:

    标签: c# azure asp.net-core azure-active-directory client-certificates


    【解决方案1】:

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

    禁用Web app client certificates 并附加并从a custom header using options.CertificateHeader = "value" 获取证书。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-07
      • 1970-01-01
      • 2020-11-12
      • 2017-08-21
      • 2018-11-20
      • 1970-01-01
      • 2017-08-25
      • 1970-01-01
      相关资源
      最近更新 更多