.NET 6.0 ASP.NET Core MVC中LDAP认证:自定义登录逻辑与模板选择
ASP.NET Core 6 MVC 结合Windows 2016 LDAP认证问题解答
一、项目模板选择建议
- 如果仅需LDAP表单认证,无需本地用户管理(注册、密码重置等),优先选Empty或MVC模板:无需引入Identity框架,自定义登录逻辑更灵活,项目更轻量。
- 如果后续需要结合本地用户扩展(如给LDAP用户分配本地权限),或用到Identity的角色、Claims管理功能,可选择Individual Accounts模板:但需禁用默认的注册、密码重置等无关功能,替换登录逻辑为自定义LDAP认证。
- 不建议选Windows Authentication模板:该模板是基于当前系统用户的自动登录,不符合手动输入账号密码的表单认证场景。
二、让登录Action使用自定义LDAP认证逻辑
默认的Identity登录Action无法直接修改,需自定义登录流程,步骤如下:
1. 配置Cookie认证中间件
在Program.cs中添加Cookie认证配置,并确保中间件顺序正确:
// 添加Cookie认证 builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Account/Login"; // 未认证时跳转的登录页 options.AccessDeniedPath = "/Account/AccessDenied"; // 权限不足跳转页 options.ExpireTimeSpan = TimeSpan.FromHours(8); // 登录有效期 }); // 中间件顺序:路由 → 认证 → 授权 app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
2. 创建自定义登录视图模型
public class LoginViewModel { [Required] [Display(Name = "用户名")] public string UserName { get; set; } [Required] [DataType(DataType.Password)] [Display(Name = "密码")] public string Password { get; set; } [Display(Name = "记住我")] public bool RememberMe { get; set; } }
3. 编写自定义Account控制器及登录Action
注入IAuthenticationService,调用LDAP认证逻辑,验证通过后生成Claims并完成登录:
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using System.Security.Claims; public class AccountController : Controller { private readonly IAuthenticationService _ldapAuthService; public AccountController(IAuthenticationService ldapAuthService) { _ldapAuthService = ldapAuthService; } [HttpGet] public IActionResult Login(string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (!ModelState.IsValid) { return View(model); } // 调用LDAP认证服务验证账号密码 var authenticatedUser = _ldapAuthService.Login(model.UserName, model.Password); if (authenticatedUser != null) { // 构建用户Claims var claims = new List<Claim> { new Claim(ClaimTypes.Name, authenticatedUser.UserName), new Claim(ClaimTypes.DisplayName, authenticatedUser.DisplayName) // 可添加更多从LDAP获取的用户属性,如部门、邮箱等 }; var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = model.RememberMe, RedirectUri = returnUrl ?? Url.Action("Index", "Home") }; // 完成登录 await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); return LocalRedirect(returnUrl ?? Url.Action("Index", "Home")); } // 认证失败,添加错误信息 ModelState.AddModelError(string.Empty, "用户名或密码不正确"); return View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index", "Home"); } }
4. 创建登录视图(Views/Account/Login.cshtml)
@model LoginViewModel @{ ViewData["Title"] = "登录"; } <h1>@ViewData["Title"]</h1> <div class="row"> <div class="col-md-4"> <form asp-action="Login" asp-route-returnUrl="@ViewData["ReturnUrl"]"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="UserName" class="control-label"></label> <input asp-for="UserName" class="form-control" /> <span asp-validation-for="UserName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password" class="control-label"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group"> <div class="checkbox"> <label asp-for="RememberMe"> <input asp-for="RememberMe" /> @Html.DisplayNameFor(model => model.RememberMe) </label> </div> </div> <div class="form-group"> <input type="submit" value="登录" class="btn btn-primary" /> </div> </form> </div> </div>
三、已完成的LDAP配置整理
以下是你已经实现的LDAP基础配置,可继续沿用:
1. 安装依赖包
安装NuGet包 Microsoft.Windows.Compatibility,用于支持LDAP相关的DirectoryEntry和DirectorySearcher类。
2. LDAP配置类与配置文件
- 定义
LdapConfig类:
public class LdapConfig { public string Path { get; set; } public string UserDomainName { get; set; } }
- 在
appsettings.json中添加LDAP节点:
{ "Ldap": { "Path": "<<LDAP Path>>", "UserDomainName": "<<Domain Name>>" } }
3. 配置依赖注入
在Program.cs中读取LDAP配置并注册认证服务:
// 读取LDAP配置 builder.Services.Configure<LdapConfig>(builder.Configuration.GetSection("Ldap")); // 注册LDAP认证服务 builder.Services.AddScoped<IAuthenticationService, LdapAuthenticationService>();
4. 认证服务接口与实现
IAuthenticationService接口:
public interface IAuthenticationService { User Login(string userName, string password); }
User数据模型:
public class User { public string UserName { get; set; } public string DisplayName { get; set; } // 其他属性可按需扩展 }
LdapAuthenticationService实现类:
public class LdapAuthenticationService : IAuthenticationService { private const string DisplayNameAttribute = "DisplayName"; private const string SAMAccountNameAttribute = "SAMAccountName"; private readonly LdapConfig config; public LdapAuthenticationService(IOptions<LdapConfig> config) { this.config = config.Value; } public User Login(string userName, string password) { try { using (DirectoryEntry entry = new DirectoryEntry(config.Path, config.UserDomainName + "\\" + userName, password)) { using (DirectorySearcher searcher = new DirectorySearcher(entry)) { searcher.Filter = string.Format("({0}={1})", SAMAccountNameAttribute, userName); searcher.PropertiesToLoad.Add(DisplayNameAttribute); searcher.PropertiesToLoad.Add(SAMAccountNameAttribute); var result = searcher.FindOne(); if (result != null) { var displayName = result.Properties[DisplayNameAttribute]; var samAccountName = result.Properties[SAMAccountNameAttribute]; return new User { DisplayName = displayName == null || displayName.Count <= 0 ? null : displayName[0].ToString(), UserName = samAccountName == null || samAccountName.Count <= 0 ? null : samAccountName[0].ToString() }; } } } } catch (Exception ex) { // 记录认证失败异常 } return null; } }
内容的提问来源于stack exchange,提问作者John John
相关产品推荐
相关产品推荐

