You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 07:07:43