.NET 8 + Kestrel如何同时支持Basic与Negotiate认证方案?
解决方案
1. 注册双认证方案(不指定单一默认)
在ConfigureServices中同时注册Negotiate和Basic认证,不通过AddAuthentication指定默认方案,转而通过授权策略允许两种认证方式:
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication() .AddNegotiate() .AddBasic(options => { options.Realm = "MyReallyRealm"; options.SuppressWWWAuthenticateHeader = false; options.Events = new BasicAuthenticationEvents { OnValidateCredentials = ValidateCredentials }; }); // 配置全局授权策略,允许两种认证方案通过 services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .AddAuthenticationSchemes("Negotiate", "Basic") .RequireAuthenticatedUser() .Build(); }); }
2. 自定义挑战逻辑,返回多认证头
默认ASP.NET仅返回默认挑战方案的单个WWW-Authenticate头,需自定义处理程序在401时添加所有所需头:
首先扩展认证配置,添加自定义挑战方案:
services.AddAuthentication(options => { options.DefaultChallengeScheme = "MultiAuthChallenge"; }) .AddNegotiate() .AddBasic(options => { options.Realm = "MyReallyRealm"; options.SuppressWWWAuthenticateHeader = false; options.Events = new BasicAuthenticationEvents { OnValidateCredentials = ValidateCredentials }; }) // 注册自定义挑战处理程序 .AddScheme<AuthenticationSchemeOptions, MultiAuthChallengeHandler>("MultiAuthChallenge", null);
然后实现自定义挑战处理程序:
public class MultiAuthChallengeHandler : AuthenticationHandler<AuthenticationSchemeOptions> { private readonly IOptionsMonitor<BasicAuthenticationOptions> _basicOptions; public MultiAuthChallengeHandler( IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IOptionsMonitor<BasicAuthenticationOptions> basicOptions) : base(options, logger, encoder, clock) { _basicOptions = basicOptions; } protected override Task<AuthenticateResult> HandleAuthenticateAsync() { // 仅处理挑战,不参与认证逻辑 return Task.FromResult(AuthenticateResult.NoResult()); } protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { Response.StatusCode = StatusCodes.Status401Unauthorized; // 添加Negotiate认证头 Response.Headers.Append(HeaderNames.WWWAuthenticate, "Negotiate"); // 添加带Realm的Basic认证头 Response.Headers.Append(HeaderNames.WWWAuthenticate, $"Basic realm=\"{_basicOptions.CurrentValue.Realm}\""); // 如需支持NTLM可添加此行 // Response.Headers.Append(HeaderNames.WWWAuthenticate, "NTLM"); await Task.CompletedTask; } }
3. 确认中间件顺序
在Configure方法中确保认证、授权中间件顺序正确,且位于业务处理逻辑之前:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); // 认证中间件必须在授权中间件之前 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { // 适配无路由WebDAV的端点配置,或替换为自定义WebDAV处理中间件 endpoints.Map("/", async context => { await context.Response.WriteAsync("WebDAV Service"); }); }); }
效果验证
- 客户端发送Basic认证请求时,Basic处理程序会触发
OnValidateCredentials逻辑; - 客户端发送Negotiate/NTLM请求时,Negotiate处理程序会处理Windows认证;
- 认证失败时,响应头会同时返回
Negotiate和Basic realm="MyReallyRealm"(如需NTLM可自行添加)。
内容的提问来源于stack exchange,提问作者sawie
相关产品推荐
相关产品推荐

