.NET 8 Blazor Server集成LDAP认证:用户信息为空及Handler失效问题
.NET 8 Blazor Server LDAP认证问题解决方案
核心问题梳理
在集成LDAP认证时遇到两个关键问题:
HttpContext.User相关信息(Claims、IsAuthenticated、Email)均为空/False- 自定义
LdapAuthenticationHandler未生效
一、修复认证服务配置(Program.cs)
取消认证相关注释,并调整中间件顺序与配置:
using Microsoft.AspNetCore.Authentication; using RCBuisinessLogic.Authentication; using RCBuisinessLogic.DataAccess; using RCWebApp.Components; var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpContextAccessor(); builder.Services.AddSingleton<IConfiguration>(builder.Configuration); builder.Services.AddSingleton<BaseDAL>(); builder.Services.AddSingleton<AuthenticationConfiguration>(); builder.Services.AddTransient<UserInformation>(); // 注册Razor组件服务 builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // 配置认证服务 builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "LDAP"; options.DefaultChallengeScheme = "LDAP"; }) .AddScheme<AuthenticationSchemeOptions, LdapAuthenticationHandler>("LDAP", options => { }); // 配置授权策略 builder.Services.AddAuthorization(options => { options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser()); }); var app = builder.Build(); // 确保认证、授权中间件顺序正确(必须在路由前) app.UseAuthentication(); app.UseAuthorization(); // 登录重定向逻辑(修复空引用问题) app.Use(async (context, next) => { if (context.User?.Identity?.IsAuthenticated == true && !context.Request.Path.StartsWithSegments("/userlogin")) { context.Response.Redirect("/dashboard"); return; } await next(); }); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();
二、修正自定义认证Handler逻辑
原Handler存在表单读取适配问题,调整后兼容Blazor Server交互模式:
using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using RCBuisinessLogic.Authentication; using System.DirectoryServices; using System.Security.Claims; using System.Text.Encodings.Web; public class LdapAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> { private readonly UserInformation _userInformation; public LdapAuthenticationHandler( IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, UserInformation userInformation) : base(options, logger, encoder, clock) { _userInformation = userInformation; } // 此方法主要用于API式登录验证,Blazor交互式组件需手动触发认证 protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { var authHeader = Request.Headers.Authorization.ToString(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic ")) { return AuthenticateResult.NoResult(); } var credentials = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authHeader.Substring(6))).Split(':'); var username = credentials[0]; var password = credentials[1]; string domain = "mycorp.com"; if (AuthenticateWithLdap(username, password, domain, out var userId, out var email)) { var claims = new List<Claim> { new Claim(ClaimTypes.Name, username), new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Email, email) }; var claimsIdentity = new ClaimsIdentity(claims, Scheme.Name); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); var ticket = new AuthenticationTicket(claimsPrincipal, Scheme.Name); return AuthenticateResult.Success(ticket); } return AuthenticateResult.Fail("Invalid username or password."); } private bool AuthenticateWithLdap(string username, string password, string domain, out string userId, out string email) { userId = null; email = null; try { var ldapPath = $"LDAP://{domain}"; var fullUsername = username.Contains("@") ? username : $"{domain}\\{username}"; using var de = new DirectoryEntry(ldapPath, fullUsername, password); de.RefreshCache(); // 触发LDAP绑定验证凭证 using var ds = new DirectorySearcher(de) { Filter = $"(sAMAccountName={EscapeLdapSearchFilter(username)})", PropertiesToLoad = { "sAMAccountName", "mail" } }; var result = ds.FindOne(); if (result != null) { userId = result.Properties["sAMAccountName"][0].ToString(); email = result.Properties.Contains("mail") ? result.Properties["mail"][0].ToString() : string.Empty; return true; } } catch (DirectoryServicesCOMException ex) { Logger.LogError(ex, "LDAP authentication failed"); } return false; } private static string EscapeLdapSearchFilter(string searchFilter) { var escape = new System.Text.StringBuilder(); foreach (char c in searchFilter) { switch (c) { case '\\': escape.Append(@"\5c"); break; case '*': escape.Append(@"\2a"); break; case '(': escape.Append(@"\28"); break; case ')': escape.Append(@"\29"); break; case '\0': escape.Append(@"\00"); break; case '/': escape.Append(@"\2f"); break; default: escape.Append(c); break; } } return escape.ToString(); } }
三、修复登录组件逻辑(UserLogin.razor)
原代码仅验证LDAP凭证,未将用户信息写入HttpContext,调整后完成完整认证流程:
@page "/userlogin" @using System.ComponentModel.DataAnnotations @using System.Security.Claims @using Microsoft.AspNetCore.Authentication @using System.DirectoryServices @using RCBuisinessLogic @using RCBuisinessLogic.Authentication @using RCWebApp.Models @rendermode InteractiveServer @inject IHttpContextAccessor HttpContextAccessor @inject NavigationManager NavigationManager @inject UserInformation UserInformation <div class="user-login" style="height: 630px;"> <EditForm Model="@Login" OnSubmit="HandleLogin" FormName="UserLoginForm"> <DataAnnotationsValidator /> <ValidationSummary /> <div class="form-group"> <label for="UserName">Username:</label> <InputText id="UserName" class="form-control" @bind-Value="Login.UserName" /> <ValidationMessage For="@(() => Login.UserName)" /> </div> <div class="form-group"> <label for="Password">Password:</label> <InputText id="Password" class="form-control" @bind-Value="Login.Password" Type="password" /> <ValidationMessage For="@(() => Login.Password)" /> </div> <button type="submit" class="btn btn-primary">Login</button> @if (!string.IsNullOrEmpty(ErrorMessage)) { <div class="text-danger mt-2">@ErrorMessage</div> } </EditForm> </div> @code { private Login Login { get; set; } = new Login(); private string ErrorMessage { get; set; } = string.Empty; private async Task HandleLogin() { ErrorMessage = string.Empty; string domain = "mycorp.com"; string ldapPath = $"LDAP://{domain}"; try { if (AuthenticateWithLdap(ldapPath, domain, Login.UserName, Login.Password, out var userId, out var email)) { // 创建用户Claims var claims = new List<Claim> { new Claim(ClaimTypes.Name, Login.UserName), new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Email, email) }; // 完成登录,将用户信息写入HttpContext var claimsIdentity = new ClaimsIdentity(claims, "LDAP"); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); await HttpContextAccessor.HttpContext.SignInAsync("LDAP", claimsPrincipal); // 跳转至仪表盘 NavigationManager.NavigateTo("/dashboard"); } else { ErrorMessage = "Invalid username or password."; } } catch (Exception ex) { ErrorMessage = $"Login failed: {ex.Message}"; } } private bool AuthenticateWithLdap(string ldapPath, string domain, string userName, string password, out string userId, out string email) { userId = null; email = null; try { var fullUsername = userName.Contains("@") ? userName : $"{domain}\\{userName}"; using var de = new DirectoryEntry(ldapPath, fullUsername, password); de.RefreshCache(); // 触发LDAP绑定验证凭证 using var ds = new DirectorySearcher(de) { Filter = $"(sAMAccountName={EscapeLdapSearchFilter(userName)})", PropertiesToLoad = { "sAMAccountName", "mail" } }; var result = ds.FindOne(); if (result != null) { userId = result.Properties["sAMAccountName"][0].ToString(); email = result.Properties.Contains("mail") ? result.Properties["mail"][0].ToString() : string.Empty; _ = UserInformation.GetByName($"{domain}\\{userName}", email); return true; } } catch (DirectoryServicesCOMException ex) { ErrorMessage = ex.ErrorCode == -2147023570 ? "Invalid username or password." : $"LDAP server error: {ex.Message}"; } return false; } private static string EscapeLdapSearchFilter(string searchFilter) { var escape = new System.Text.StringBuilder(); foreach (char c in searchFilter) { switch (c) { case '\\': escape.Append(@"\5c"); break; case '*': escape.Append(@"\2a"); break; case '(': escape.Append(@"\28"); break; case ')': escape.Append(@"\29"); break; case '\0': escape.Append(@"\00"); break; case '/': escape.Append(@"\2f"); break; default: escape.Append(c); break; } } return escape.ToString(); } }
四、问题根源解析
HttpContext.User为空:
原登录逻辑仅验证LDAP凭证,未调用SignInAsync将用户Claims写入HttpContext,导致认证状态未持久化。Blazor Server交互式组件需手动完成登录流程。自定义Handler未生效:
原Handler依赖读取Request.Form,但Blazor Server交互式组件的表单提交通过SignalR传递,而非传统HTTP表单,因此无法触发自动认证逻辑。Handler主要用于API端点的后续身份验证,登录流程需在组件内手动实现。
内容的提问来源于stack exchange,提问作者tRuEsAtM
相关产品推荐
相关产品推荐

