.NET Core中Windows与ApiKey双认证失效问题排查
问题原因分析
- 认证服务注册错误:两次调用
AddAuthentication会覆盖认证配置,导致首次注册的Negotiate方案可能未被正确纳入认证体系,同时默认认证方案被重复设置。 - 授权策略逻辑不足:默认的
RequireAuthenticatedUser()仅要求任一认证方案通过即可放行,并未强制要求两种认证同时验证通过。即使ApiKey验证失败,只要NTLM认证成功,请求就会被允许进入控制器方法。
解决方案
1. 修复认证服务注册方式
将两次分离的AddAuthentication调用合并为链式调用,确保两种认证方案都被正确注册:
services.AddAuthentication() .AddNegotiate() // 注册NTLM认证方案 .AddApiKey(); // 注册自定义ApiKey认证方案
无需设置默认认证方案,因为后续授权策略会明确指定需要验证的方案。
2. 修改授权策略,强制双认证通过
替换原策略中的RequireAuthenticatedUser(),使用自定义断言检查请求是否同时通过两种认证:
services.AddAuthorization(options => { options.AddPolicy("AuthorizePolicy", policy => { // 指定需要验证的两个认证方案 policy.AddAuthenticationSchemes(NegotiateDefaults.AuthenticationScheme, ApiKeyDefaults.AuthenticationScheme); // 断言要求两种认证的身份都存在于Principal中 policy.RequireAssertion(context => { bool hasValidNtlmAuth = context.User.Identities.Any(id => id.AuthenticationType == NegotiateDefaults.AuthenticationScheme && id.IsAuthenticated); bool hasValidApiKeyAuth = context.User.Identities.Any(id => id.AuthenticationType == ApiKeyDefaults.AuthenticationScheme && id.IsAuthenticated); return hasValidNtlmAuth && hasValidApiKeyAuth; }); }); });
该断言会检查当前用户主体是否包含来自两种认证方案的有效已认证身份,只有两者同时满足时,才会允许访问。
3. (可选)使用自定义授权处理器增强控制
如果需要更精细的验证逻辑(比如确保两种认证对应的是同一个用户),可以创建自定义授权处理器:
定义授权要求和处理器
// 自定义授权要求 public class DualAuthenticationRequirement : IAuthorizationRequirement { } // 授权处理器 public class DualAuthenticationHandler : AuthorizationHandler<DualAuthenticationRequirement> { private readonly IAuthenticationService _authService; private readonly IHttpContextAccessor _httpContextAccessor; public DualAuthenticationHandler(IAuthenticationService authService, IHttpContextAccessor httpContextAccessor) { _authService = authService; _httpContextAccessor = httpContextAccessor; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, DualAuthenticationRequirement requirement) { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) { context.Fail(); return; } // 分别验证两种认证方案 var ntlmResult = await _authService.AuthenticateAsync(httpContext, NegotiateDefaults.AuthenticationScheme); var apiKeyResult = await _authService.AuthenticateAsync(httpContext, ApiKeyDefaults.AuthenticationScheme); // 验证两种认证都成功,且用户身份一致(可选) if (ntlmResult.Succeeded && apiKeyResult.Succeeded) { // 可选:检查NTLM用户和ApiKey用户是否匹配 string ntlmUserName = ntlmResult.Principal.Identity.Name; string apiKeyUserName = apiKeyResult.Principal.FindFirst(ClaimTypes.Name)?.Value; if (ntlmUserName.Equals(apiKeyUserName, StringComparison.OrdinalIgnoreCase)) { context.Succeed(requirement); return; } } context.Fail(); } }
注册处理器并更新策略
// 注册自定义授权处理器 services.AddScoped<IAuthorizationHandler, DualAuthenticationHandler>(); // 更新授权策略 services.AddAuthorization(options => { options.AddPolicy("AuthorizePolicy", policy => { policy.Requirements.Add(new DualAuthenticationRequirement()); }); });
验证方式
- 使用Postman/Insomnia发送请求,仅携带有效NTLM凭证但无效ApiKey:应返回
403 Forbidden(或通过自定义逻辑返回401 Unauthorized)。 - 携带有效NTLM凭证和有效ApiKey:请求正常进入控制器方法。
- 仅携带有效ApiKey但无效NTLM凭证:同样返回权限错误。
内容的提问来源于stack exchange,提问作者GvarAlex
相关产品推荐
相关产品推荐

