У меня есть веб-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.
Примечания:
- При локальном запуске моего API сертификат правильно подбирается и проходит через утверждения. При запуске в моей службе приложений кажется, что сертификат удаляется службой приложений. Вот почему у меня есть этот
if (env.IsDevelopment())
оператор, чтобы выбирать между утверждениями и X-ARR-ClientCert
заголовком. - Когда я исключаю все пути из моих «Входящих клиентских сертификатов», заголовок
X-ARR-ClientCert
не передается. Когда я удаляю исключение, он правильно передает заголовок.
Есть ли у меня какой-либо способ:
- Получить сертификат клиента, который будет передаваться через утверждения пользователей в моем приложении службы приложений продукта?
- Получить службу приложений для передачи заголовка
X-ARR-ClientCert
без указания наличия сертификата клиента? - Что-то мне не хватает / способ лучше сделать?