.NET 7内网用户Windows身份验证(SSO)与外网用户表单验证实现问询
实现ASP.NET Core 7 内外网差异化身份验证方案
1. 配置双身份验证方案
在Program.cs中同时注册Windows认证和表单Cookie认证方案,并添加AD验证服务:
var builder = WebApplication.CreateBuilder(args); // 注册认证服务,不指定默认方案,后续动态选择 builder.Services.AddAuthentication() // Windows认证方案 .AddWindows(options => { options.AuthenticationScheme = "Windows"; options.Events = new WindowsAuthenticationEvents { OnAuthenticated = context => { // 可在此添加用户声明、角色等自定义逻辑 return Task.CompletedTask; } }; }) // 表单登录Cookie认证方案 .AddCookie("Forms", options => { options.LoginPath = "/Account/Login"; options.AccessDeniedPath = "/Account/AccessDenied"; options.ExpireTimeSpan = TimeSpan.FromHours(8); }); // 注册AD验证服务 builder.Services.AddScoped<IAdAuthenticationService, AdAuthenticationService>(); builder.Services.AddControllersWithViews();
2. 实现内外网请求判断逻辑
编写服务类识别请求来源,支持IP段或域名两种判断方式:
public interface IIntranetDetector { bool IsIntranetRequest(HttpContext context); } public class IntranetDetector : IIntranetDetector { // 替换为你的内网IP段 private readonly string[] _intranetIpRanges = new[] { "192.168.", "10.", "172.16." }; // 也可通过域名判断,比如内网专属域名 // private readonly string _intranetHost = "intranet.yourcompany.com"; public bool IsIntranetRequest(HttpContext context) { // 方式1:IP段判断 var remoteIp = context.Connection.RemoteIpAddress?.ToString(); if (!string.IsNullOrEmpty(remoteIp)) { return _intranetIpRanges.Any(range => remoteIp.StartsWith(range)); } // 方式2:域名判断(注释掉IP判断,启用此逻辑) // var host = context.Request.Host.Host; // return host.Equals(_intranetHost, StringComparison.OrdinalIgnoreCase); return false; } }
在Program.cs中注册该服务:
builder.Services.AddScoped<IIntranetDetector, IntranetDetector>();
3. 动态选择认证方案的中间件
编写中间件,根据请求来源自动触发对应认证流程:
public class AuthSchemeSelectorMiddleware { private readonly RequestDelegate _next; public AuthSchemeSelectorMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, IIntranetDetector intranetDetector) { // 仅对未认证用户执行逻辑 if (!context.User.Identity?.IsAuthenticated ?? true) { if (intranetDetector.IsIntranetRequest(context)) { // 内网请求:触发Windows SSO认证 await context.ChallengeAsync("Windows"); } else { // 外网请求:跳转到表单登录页 await context.ChallengeAsync("Forms"); } } await _next(context); } } // 扩展方法简化中间件注册 public static class MiddlewareExtensions { public static IApplicationBuilder UseAuthSchemeSelector(this IApplicationBuilder app) { return app.UseMiddleware<AuthSchemeSelectorMiddleware>(); } }
在Program.cs的管道中添加该中间件(位置需在UseAuthentication之后、UseAuthorization之前):
var app = builder.Build(); // 其他中间件(静态文件、错误处理等) app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); // 添加自定义认证方案选择中间件 app.UseAuthSchemeSelector(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
4. 实现表单登录与AD验证逻辑
首先定义AD验证服务接口及实现:
public interface IAdAuthenticationService { Task<bool> ValidateCredentials(string username, string password); Task<ClaimsPrincipal> CreateUserPrincipal(string username); } public class AdAuthenticationService : IAdAuthenticationService { // 替换为你的AD域名 private readonly string _adDomain = "yourcompany.com"; public async Task<bool> ValidateCredentials(string username, string password) { using var context = new PrincipalContext(ContextType.Domain, _adDomain); // 异步验证AD凭据 return await Task.Run(() => context.ValidateCredentials(username, password)); } public async Task<ClaimsPrincipal> CreateUserPrincipal(string username) { using var context = new PrincipalContext(ContextType.Domain, _adDomain); var userPrincipal = await Task.Run(() => UserPrincipal.FindByIdentity(context, username)); if (userPrincipal == null) return null; // 构建用户声明 var claims = new List<Claim> { new Claim(ClaimTypes.Name, userPrincipal.DisplayName), new Claim(ClaimTypes.NameIdentifier, userPrincipal.SamAccountName), new Claim(ClaimTypes.Email, userPrincipal.EmailAddress ?? string.Empty) }; var identity = new ClaimsIdentity(claims, "Forms"); return new ClaimsPrincipal(identity); } }
然后编写登录控制器:
public class AccountController : Controller { private readonly IAdAuthenticationService _adAuthService; public AccountController(IAdAuthenticationService adAuthService) { _adAuthService = adAuthService; } [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) { var isValid = await _adAuthService.ValidateCredentials(model.Username, model.Password); if (isValid) { var userPrincipal = await _adAuthService.CreateUserPrincipal(model.Username); await HttpContext.SignInAsync("Forms", userPrincipal); return LocalRedirect(returnUrl ?? Url.Action("Index", "Home")); } ModelState.AddModelError(string.Empty, "用户名或密码错误"); } return View(model); } [HttpPost] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync("Forms"); return RedirectToAction("Index", "Home"); } } // 登录视图模型 public class LoginViewModel { [Required] [Display(Name = "用户名")] public string Username { get; set; } [Required] [DataType(DataType.Password)] [Display(Name = "密码")] public string Password { get; set; } }
5. 关键注意事项
- Windows认证依赖服务器环境:IIS需开启Windows Authentication功能;Kestrel仅支持Windows系统运行,且进程需有域权限。
- 内网用户若仍弹出Windows认证窗口,需将站点添加到浏览器的「本地Intranet」区域(IE/Edge可通过Internet选项配置)。
- AD验证需确保服务器能访问域控制器,且应用进程有读取AD用户信息的权限。
- 权限控制直接使用
[Authorize]属性即可,无需指定认证方案,中间件会自动处理。
内容的提问来源于stack exchange,提问作者zSynopsis
相关产品推荐
相关产品推荐

