我的 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 身份验证和客户端证书身份验证。
我已按照这些指南到达我所在的位置:
- Azure Active Directory with ASP.NET Core
- Configure TLS mutual authentication for Azure App Service
- Policy-based authorization in ASP.NET Core
- 使用多个 JWT 承载身份验证
这是我目前的设置:
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())
声明可以在 Claims 和 X-ARR-ClientCert
header 之间进行选择。
- 当我排除 "Incoming client certificates" 中的所有路径时,
X-ARR-ClientCert
header 不会通过。当我删除排除项时,它会正确传递 header。
我有什么办法可以:
- 获取要通过我的产品应用服务应用中的用户声明传递的客户端证书?
- 获取应用服务以通过
X-ARR-ClientCert
header 而无需 强制要求存在客户端证书?
- 有什么我遗漏的/更好的方法吗?
正如您已经发现的那样,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"。
我有一个 App Service 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
- 使用多个 JWT 承载身份验证
这是我目前的设置:
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())
声明可以在 Claims 和X-ARR-ClientCert
header 之间进行选择。 - 当我排除 "Incoming client certificates" 中的所有路径时,
X-ARR-ClientCert
header 不会通过。当我删除排除项时,它会正确传递 header。
我有什么办法可以:
- 获取要通过我的产品应用服务应用中的用户声明传递的客户端证书?
- 获取应用服务以通过
X-ARR-ClientCert
header 而无需 强制要求存在客户端证书? - 有什么我遗漏的/更好的方法吗?
正如您已经发现的那样,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"。