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
相关产品推荐
相关产品推荐

