Blazor Server中LDAP与自定义AuthenticationStateProvider的集成方案咨询
I'm trying to implement authentication using LDAP with a custom AuthenticationStateProvider in ASP.NET. I've already completed the following configurations:
- Added
services.AddScoped<AuthenticationStateProvider,Login_Logic>();to the service collection - Configured
app.UseAuthentication();andapp.UseAuthorization();inStartup.cs
However, I'm unclear on how to integrate the LDAP authentication logic with my custom AuthenticationStateProvider. Here's my current Login_Logic class code:
public class Login_Logic:AuthenticationStateProvider { public UserData user { get; set; } public string loginattempt { get; set; } public bool userisauthenticated { get; set; } SearchResult result { get; set; } public override Task<AuthenticationState> GetAuthenticationStateAsync() { var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, result.GetDirectoryEntry().Name) }, "Fake authentication type"); } private async Task dothething() { DirectoryEntry entry = new DirectoryEntry(); entry = new DirectoryEntry("LDAP://myldap"); entry.Username = user.UserName; entry.Password = user.Password; DirectorySearcher search = new DirectorySearcher(entry); search.Filter = "(SAMAccountName=" + user.UserName + ")"; search.PropertiesToLoad.Add("phone"); if (user.UserName != null && user.Password != null) { try { result = search.FindOne(); if (result != null) { loginattempt = "Success"; userisauthenticated = true; } } catch { loginattempt = "Wrong username or password"; } } }
I'd appreciate guidance on how to integrate these two parts.
Great question! Let's break down how to properly wire up your LDAP logic with the AuthenticationStateProvider step by step:
1. Fix the GetAuthenticationStateAsync method
Right now, your method doesn't return a valid Task<AuthenticationState> — that's a critical gap. This method needs to return the current authentication state at all times:
- For unauthenticated users, return an empty
ClaimsPrincipal - For authenticated users, return a principal built from the LDAP user's data
2. Expose a public login method to trigger LDAP validation
Your private dothething method should be converted to a public, async method that your UI (like a login page) can call. This method will handle the LDAP check, and if successful, update the authentication state.
3. Notify the framework of state changes
After a login (success or failure), you must call NotifyAuthenticationStateChanged to let ASP.NET/Blazor know the user's authentication status has changed. This triggers updates across all components that rely on AuthenticationState.
4. Secure your LDAP filter
Your current filter uses string concatenation, which opens you up to LDAP injection attacks. Use a properly formatted filter instead.
Full corrected example code
Here's how your Login_Logic class should look after these adjustments:
using System.Security.Claims; using System.DirectoryServices; using System.Threading.Tasks; using System.Collections.Generic; public class Login_Logic : AuthenticationStateProvider { private ClaimsPrincipal _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); public string LoginAttemptStatus { get; private set; } public bool IsUserAuthenticated => _currentUser.Identity.IsAuthenticated; public override Task<AuthenticationState> GetAuthenticationStateAsync() { return Task.FromResult(new AuthenticationState(_currentUser)); } public async Task<bool> Login(UserData user) { if (string.IsNullOrEmpty(user.UserName) || string.IsNullOrEmpty(user.Password)) { LoginAttemptStatus = "Username and password are required"; return false; } try { // Validate credentials by attempting to bind to the LDAP server using var entry = new DirectoryEntry("LDAP://myldap", user.UserName, user.Password); // Accessing NativeObject forces credential validation _ = entry.NativeObject; // Fetch additional user details if credentials are valid using var searcher = new DirectorySearcher(entry) { Filter = $"(&(objectClass=user)(SAMAccountName={user.UserName}))", PropertiesToLoad = { "displayName", "mail", "telephoneNumber" } }; var searchResult = searcher.FindOne(); if (searchResult == null) { LoginAttemptStatus = "User not found in LDAP directory"; return false; } // Build claims from LDAP properties var claims = new List<Claim> { new Claim(ClaimTypes.Name, searchResult.Properties["displayName"][0]?.ToString() ?? user.UserName), new Claim(ClaimTypes.NameIdentifier, user.UserName), new Claim(ClaimTypes.Email, searchResult.Properties["mail"][0]?.ToString() ?? string.Empty), new Claim("Phone", searchResult.Properties["telephoneNumber"][0]?.ToString() ?? string.Empty) }; // Create authenticated identity and update current user var identity = new ClaimsIdentity(claims, "LDAPAuthentication"); _currentUser = new ClaimsPrincipal(identity); // Notify the framework that authentication state has changed NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); LoginAttemptStatus = "Login successful"; return true; } catch (DirectoryServicesCOMException ex) { // Handle specific LDAP error codes for better user feedback LoginAttemptStatus = ex.ErrorCode switch { -2147023570 => "Invalid username or password", -2147024891 => "Account locked or disabled", _ => $"Login failed: {ex.Message}" }; return false; } catch (Exception ex) { LoginAttemptStatus = $"Unexpected error: {ex.Message}"; return false; } } public void Logout() { // Reset to unauthenticated state _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); LoginAttemptStatus = "Logged out successfully"; } }
Key usage tips:
- Call
Loginfrom your UI: InjectLogin_Logicinto your login component, then callawait Login(userData)when the user submits their credentials. - Display feedback: Use the
LoginAttemptStatusproperty to show success/error messages to the user. - Adjust LDAP settings: Update the
LDAP://myldappath to match your actual LDAP server (e.g.,LDAP://yourdomain.comfor Active Directory). - Define
UserData: Make sure yourUserDataclass hasUserNameandPasswordproperties to pass into theLoginmethod.
内容的提问来源于stack exchange,提问作者Kar816

