You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6中Azure AD与证书mTLS双重认证配置咨询

Azure App Service中ASP.NET Core 6结合Azure AD与证书认证配置方案

一、Azure App Service端配置证书认证

  1. 上传信任证书
    • 登录Azure门户,进入目标App Service资源,左侧菜单选择TLS/SSL设置 -> 证书上传。
    • 选择**公钥证书(.cer)**格式,上传你信任的根CA证书(或允许的客户端证书公钥),完成后保存。
  2. 启用客户端证书强制要求
    • 进入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"
  ]
}

三、验证测试

  1. 将允许的客户端证书安装到访问设备的个人证书存储中。
  2. 访问网站:首先会跳转到Azure AD登录页面,完成身份验证后,浏览器会弹出证书选择窗口,选择已安装的合法证书。
  3. 若证书合法且Azure AD认证通过,即可正常访问网站;任一验证失败都会被拒绝访问。

内容的提问来源于stack exchange,提问作者Craig Walker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 13:54:55