标记AllowAnonymous的Endpoint仍要求身份验证(.NET7.0)
问题排查与解决方案
1. 检查自定义BasicAuth中间件的逻辑
多数情况下,这个问题是因为自定义中间件没有检查当前请求的端点是否携带[AllowAnonymous]属性,直接强制执行身份验证,导致标记匿名的接口也返回401。
如果你的中间件是类似下面的错误写法,会跳过框架的授权逻辑,直接拦截所有请求:
public async Task InvokeAsync(HttpContext context) { var authHeader = context.Request.Headers.Authorization.FirstOrDefault(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic ")) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return; } // 验证逻辑... await _next(context); }
修复方式:在中间件中先获取当前端点的元数据,判断是否包含AllowAnonymousAttribute,如果有则跳过验证:
public async Task InvokeAsync(HttpContext context) { var endpoint = context.GetEndpoint(); // 存在匿名标记则直接放行 if (endpoint?.Metadata.GetMetadata<AllowAnonymousAttribute>() != null) { await _next(context); return; } // 执行BasicAuth验证逻辑 var authHeader = context.Request.Headers.Authorization.FirstOrDefault(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic ")) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return; } // 用户名密码验证逻辑... await _next(context); }
2. 确认中间件注册顺序
在Program.cs中,中间件的注册顺序直接影响管道执行逻辑,必须严格遵循:
UseAuthentication()必须在UseAuthorization()之前- 自定义BasicAuth中间件如果是独立实现,需要放在
UseRouting()之后、UseAuthorization()之前;如果是基于标准AuthenticationHandler,只需注册认证服务即可。
正确的注册示例(标准认证方案):
var builder = WebApplication.CreateBuilder(args); // 注册自定义BasicAuth认证方案 builder.Services.AddAuthentication("BasicAuth") .AddScheme<AuthenticationSchemeOptions, BasicAuthHandler>("BasicAuth", null); builder.Services.AddAuthorization(); var app = builder.Build(); // 中间件顺序不能乱 app.UseHttpsRedirection(); app.UseAuthentication(); // 先执行认证 app.UseAuthorization(); // 再执行授权判断 app.MapControllers(); app.Run();
如果使用独立中间件,顺序应为:
app.UseRouting(); app.UseMiddleware<BasicAuthMiddleware>(); app.UseAuthentication(); app.UseAuthorization();
3. 改用ASP.NET Core标准AuthenticationHandler实现BasicAuth
直接写独立中间件容易绕开框架的授权体系,推荐继承AuthenticationHandler<AuthenticationSchemeOptions>实现标准认证方案,这样能自动兼容[AllowAnonymous]和[Authorize]标记:
public class BasicAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions> { public BasicAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { // 检查匿名标记,跳过认证 if (Context.GetEndpoint()?.Metadata.GetMetadata<AllowAnonymousAttribute>() != null) { return AuthenticateResult.NoResult(); } if (!Request.Headers.ContainsKey("Authorization")) { return AuthenticateResult.Fail("Authorization header missing"); } try { var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); var credentialsBytes = Convert.FromBase64String(authHeader.Parameter); var credentials = Encoding.UTF8.GetString(credentialsBytes).Split(':', 2); var username = credentials[0]; var password = credentials[1]; // 替换为你的实际验证逻辑(比如数据库查询) if (username == "admin" && password == "password") { var claims = new[] { new Claim(ClaimTypes.Name, username) }; var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); return AuthenticateResult.Success(ticket); } else { return AuthenticateResult.Fail("Invalid credentials"); } } catch { return AuthenticateResult.Fail("Invalid authorization header format"); } } }
注册时设置默认认证方案:
builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "BasicAuth"; options.DefaultChallengeScheme = "BasicAuth"; }) .AddScheme<AuthenticationSchemeOptions, BasicAuthHandler>("BasicAuth", options => { });
这种方式下,框架会自动处理授权逻辑,[AllowAnonymous]的接口会被跳过认证。
4. 验证控制器和接口的标记
确认SimpleLookupController的GetExtension方法正确标记了[AllowAnonymous],如果控制器本身标记了[Authorize],方法级的[AllowAnonymous]需要能覆盖控制器设置(.NET7支持这种覆盖,但要确保标记没有拼写错误):
[ApiController] [Route("api/[controller]")] // 如果控制器有[Authorize],方法级的[AllowAnonymous]依然生效 // [Authorize] public class SimpleLookupController : ControllerBase { [AllowAnonymous] [HttpGet("extension")] public IActionResult GetExtension() { return Ok("Success"); } }
内容的提问来源于stack exchange,提问作者Rachel Ambler
相关产品推荐
相关产品推荐

