【发布时间】:2020-08-22 01:30:34
【问题描述】:
我有一个应用服务 Web API,我想同时支持 Azure Active Directory 身份验证和客户端证书身份验证。
我已经按照这些指南到达了我所在的位置:
- Azure Active Directory with ASP.NET Core
- Configure TLS mutual authentication for Azure App Service
- Policy-based authorization in ASP.NET Core
- Use multiple JWT Bearer Authentication
这是我目前的设置:
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标头不会通过。当我删除排除项时,它会正确传递标题。
我有什么办法:
- 获取客户端证书以通过我的 Product App Service 应用程序中的用户声明传递?
- 让应用服务传递
X-ARR-ClientCert标头而不强制要求存在客户端证书? - 我有什么遗漏/更好的方法吗?
【问题讨论】:
标签: c# azure asp.net-core azure-active-directory client-certificates