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

ASP.NET Core Razor应用CAC证书认证实现故障求助

ASP.NET Core证书认证问题修复方案

你的问题核心出在三个关键环节:证书认证事件未完成身份凭证创建、认证方案缺失触发逻辑、自定义中间件逻辑错误。以下是针对性修复步骤:

1. 完善证书认证核心逻辑(OnCertificateValidated)

当前代码仅判断用户存在,但未生成身份凭证(ClaimsPrincipal)也未标记认证成功,导致context.User始终处于未认证状态。需补充身份凭证创建及成功标记逻辑:

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
    options.LoginPath = "/Identity/Account/Login";
    options.ExpireTimeSpan = TimeSpan.FromHours(8); // 可选:配置Cookie有效期
})
.AddCertificate("CertificateScheme", options =>
{
    options.ValidationMode = X509CertificateValidationMode.ChainTrust; // 根据需求选择验证模式
    options.Events = new CertificateAuthenticationEvents
    {
        OnCertificateValidated = async context =>
        {
            var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
            logger.LogInformation("Starting certificate validation.");

            var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
            var ediNumber = Utilities.ExtractEdiNumberFromCertificate(context.ClientCertificate);
            logger.LogInformation($"EDIPI extracted: {ediNumber}");

            var user = await userService.FindByEdipiAsync(ediNumber);
            if (user != null)
            {
                logger.LogInformation($"User found: {user.UserName}. Authenticating.");
                
                // 创建身份凭证,添加必要声明
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.NameIdentifier, user.Id),
                    new Claim(ClaimTypes.Name, user.UserName),
                    new Claim("EDIPI", ediNumber) // 自定义EDIPI声明
                };

                var identity = new ClaimsIdentity(claims, context.Scheme.Name);
                var principal = new ClaimsPrincipal(identity);

                // 将凭证绑定到上下文,标记认证成功
                context.Principal = principal;
                context.Success();
            }
            else
            {
                logger.LogWarning("User not found with EDIPI. Failing authentication.");
                context.Fail("No matching user found.");
            }
        },
        // 处理用户取消证书选择的场景
        OnAuthenticationFailed = context =>
        {
            if (context.Exception is OperationCanceledException)
            {
                context.Response.Redirect("/Identity/Account/Login");
            }
            return Task.CompletedTask;
        }
    };
});

// 配置证书认证专属授权策略
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CertificateRequired", policy =>
    {
        policy.AddAuthenticationSchemes("CertificateScheme");
        policy.RequireAuthenticatedUser();
    });
});

2. 修正自定义中间件逻辑

原中间件存在查询参数判断错误、认证成功后错误跳转问题,调整后如下:

// 注意:中间件顺序必须在UseAuthentication和UseAuthorization之后
app.UseAuthentication();
app.UseAuthorization();

app.Use(async (context, next) =>
{
    var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
    logger.LogInformation("Middleware Check: Start");

    bool isLoginPage = context.Request.Path.StartsWithSegments("/Identity/Account/Login");
    // 正确判断查询参数authenticate=yes
    bool isAuthenticatedCallback = context.Request.Query.ContainsKey("authenticate") && 
                                   context.Request.Query["authenticate"] == "yes";

    if (context.User.Identity.IsAuthenticated)
    {
        logger.LogInformation("User is authenticated");
        if (context.User.HasClaim(c => c.Type == "EDIPI"))
        {
            logger.LogInformation("User authenticated via certificate with EDIPI");
            // 认证成功后直接放行到目标页面
            await next();
            return;
        }
    }
    else
    {
        logger.LogInformation("User is not authenticated");
        // 非登录页、非API请求、HTML请求,触发证书选择提示
        if (!isLoginPage && !context.Request.Path.StartsWithSegments("/api") && 
            context.Request.Headers["Accept"].ToString().Contains("text/html"))
        {
            logger.LogInformation("Triggering certificate authentication challenge");
            // 触发证书认证方案挑战,弹出选择框
            await context.ChallengeAsync("CertificateScheme");
            return;
        }
    }

    await next();
});

3. 关键配置注意事项

  • 中间件顺序:UseAuthentication()和UseAuthorization()必须在自定义中间件之前,否则无法正确获取context.User的认证状态
  • 页面强制认证:若需特定页面强制使用证书认证,可在控制器/Action上添加[Authorize(Policy = "CertificateRequired")]属性,无需依赖中间件跳转

内容的提问来源于stack exchange,提问作者L. Wallach

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:17:07