ASP.NET Core 6中Azure AD与证书mTLS双重认证配置咨询
Azure App Service中ASP.NET Core 6结合Azure AD与证书认证配置方案
一、Azure App Service端配置证书认证
- 上传信任证书
- 登录Azure门户,进入目标App Service资源,左侧菜单选择TLS/SSL设置 -> 证书上传。
- 选择**公钥证书(.cer)**格式,上传你信任的根CA证书(或允许的客户端证书公钥),完成后保存。
- 启用客户端证书强制要求
- 进入App Service的配置 -> 常规设置,找到客户端证书模式,设置为需要客户端证书。
- 或使用Azure CLI命令快速配置:
az webapp update --name <你的应用名称> --resource-group <你的资源组名称> --set clientCertEnabled=true --set clientCertMode=Required
二、ASP.NET Core 6代码修改
修改Program.cs,同时配置Azure AD和证书认证,并设置全局授权策略要求两者同时通过:
var builder = WebApplication.CreateBuilder(args); // 添加认证服务:同时启用Azure AD和证书认证 builder.Services.AddAuthentication() // 配置Azure AD认证 .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"), OpenIdConnectDefaults.AuthenticationScheme) // 配置证书认证 .AddCertificate(options => { // 允许的证书类型,根据实际需求调整 options.AllowedCertificateTypes = CertificateTypes.All; // 验证证书有效期 options.ValidateValidityPeriod = true; // 自定义证书验证逻辑:检查证书指纹是否在允许列表中 options.Events = new CertificateAuthenticationEvents { OnCertificateValidated = context => { // 从配置读取允许的证书指纹列表 var allowedThumbprints = builder.Configuration.GetSection("AllowedCertificateThumbprints").Get<string[]>(); if (allowedThumbprints != null && allowedThumbprints.Contains(context.ClientCertificate.Thumbprint, StringComparer.OrdinalIgnoreCase)) { context.Success(); } else { context.Fail("证书指纹不在允许列表中"); } return Task.CompletedTask; } }; }); // 配置授权策略:要求同时通过Azure AD和证书认证 builder.Services.AddAuthorization(options => { options.FallbackPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() // 指定需要同时验证的两种认证方案 .AddAuthenticationSchemes( OpenIdConnectDefaults.AuthenticationScheme, CertificateAuthenticationDefaults.AuthenticationScheme) .Build(); }); // 保留原有服务配置 builder.Services.AddRazorPages() .AddMicrosoftIdentityUI(); builder.Services.AddDbContext<dbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("XXXXXX") )); builder.Services.AddScoped<LicenseService>(); builder.Services.AddScoped<SettingsService>(); builder.Services.AddControllers(); builder.Services.AddControllersWithViews(); builder.Services.AddHsts(options => { options.IncludeSubDomains = true; options.MaxAge = TimeSpan.FromDays(60); }); var app = builder.Build(); // 保留原有请求管道配置 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages(); app.MapControllers(); // 保留原有安全头中间件逻辑 app.Use(async (context, next) => { var rng = RandomNumberGenerator.Create(); var nonceBytes = new byte[32]; rng.GetBytes(nonceBytes); var scriptNonce = Convert.ToBase64String(nonceBytes); context.Items.Add("ScriptNonce", scriptNonce); rng = RandomNumberGenerator.Create(); nonceBytes = new byte[32]; rng.GetBytes(nonceBytes); var styleNonce = Convert.ToBase64String(nonceBytes); context.Items.Add("StyleNonce", styleNonce); if (app.Environment.IsDevelopment()) { context.Response.Headers.Add("Content-Security-Policy-Report-Only", string.Format("default-src 'self'; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; base-uri 'self'; object-src 'none'; script-src 'self' 'nonce-{0}'; style-src 'self' 'nonce-{1}'", scriptNonce, styleNonce)); } else { context.Response.Headers.Add("Content-Security-Policy", string.Format("default-src 'self'; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; base-uri 'self'; object-src 'none'; script-src 'self' 'nonce-{0}'; style-src 'self' 'nonce-{1}'", scriptNonce, styleNonce)); } context.Response.Headers.Add("X-Frame-Options", "DENY"); context.Response.Headers.Add("X-Xss-Protection", "1; mode=block"); //context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); added to web config for static files context.Response.Headers.Add("Referrer-Policy", "same-origin"); context.Response.Headers.Add("X-Permitted-Cross-Domain-Policies", "none"); context.Response.Headers.Add("Permissions-Policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"); await next(); }); app.Run();
补充配置
在appsettings.json中添加允许的证书指纹列表:
{ "AllowedCertificateThumbprints": [ "AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00", "FF11EE22DD33CC44BB55AA66FF77EE88DD99CC00" ] }
三、验证测试
- 将允许的客户端证书安装到访问设备的个人证书存储中。
- 访问网站:首先会跳转到Azure AD登录页面,完成身份验证后,浏览器会弹出证书选择窗口,选择已安装的合法证书。
- 若证书合法且Azure AD认证通过,即可正常访问网站;任一验证失败都会被拒绝访问。
内容的提问来源于stack exchange,提问作者Craig Walker
相关产品推荐
相关产品推荐

