调用需is_admin Claim的端点时出现404 NOT FOUND错误求助
现有配置代码
服务配置
var services = builder.Services; services.AddAuthentication(IdentityConstants.BearerScheme) .AddBearerToken(IdentityConstants.BearerScheme); services.AddAuthorizationBuilder() .SetDefaultPolicy(new([new DenyAnonymousAuthorizationRequirement()], [IdentityConstants.BearerScheme])); services.AddIdentity<AppUser, IdentityRole>() .AddEntityFrameworkStores<AppDbContext>() .AddClaimsPrincipalFactory<AppUserClaimsPrincipalFactory>();
AppUser类
public class AppUser : IdentityUser { public string? Locale { get; set; } public bool? IsAdmin { get; set; } }
AppUserClaimsPrincipalFactory类
public class AppUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<AppUser> { public AppUserClaimsPrincipalFactory( UserManager<AppUser> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor) { } protected override async Task<ClaimsIdentity> GenerateClaimsAsync(AppUser user) { var identity = await base.GenerateClaimsAsync(user); if (user.Locale is not null) { identity.AddClaim(new Claim("locale", user.Locale)); } if (user.IsAdmin == true) //IsAdmin is bool? { identity.AddClaim(new Claim("is_admin", "is_admin")); } return identity; } }
出错的端点
app.MapGet("/admin/page", () => Results.Ok("admin page")) .RequireAuthorization(config => config.RequireClaim("is_admin"));
验证Claim的端点
app.MapGet("/claims", async (SignInManager<AppUser> signInManager, HttpContext http) => { var user = await signInManager.UserManager.GetUserAsync(http.User); if (user is null) { return Results.BadRequest("User not found"); } var principal = await signInManager.CreateUserPrincipalAsync(user); return Results.Ok(principal.Claims); }) .RequireAuthorization();
问题描述
调用/admin/page端点时返回404 Not Found错误,但通过/claims端点已确认用户持有所需的is_admin Claim,需要排查原因。
可能的原因及解决方法
1. Bearer Token中未包含is_admin Claim
SignInManager.CreateUserPrincipalAsync是直接从数据库用户实体生成Principal,但Bearer Token的Claims是在颁发Token时确定的。默认AddBearerToken不会自动将自定义Claim(如is_admin、locale)添加到JWT中,导致认证后HttpContext.User中没有该Claim,最终授权失败,部分场景下会被返回为404而非预期的403。
解决方法:配置AddBearerToken的事件,在生成Token时添加自定义Claim:
services.AddAuthentication(IdentityConstants.BearerScheme) .AddBearerToken(IdentityConstants.BearerScheme, options => { options.Events = new BearerTokenEvents { async function OnCreatingToken(context) { var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<AppUser>>(); var user = await userManager.GetUserAsync(context.Principal); if (user?.IsAdmin == true) { context.Claims.Add(new Claim("is_admin", "is_admin")); } if (!string.IsNullOrEmpty(user?.Locale)) { context.Claims.Add(new Claim("locale", user.Locale)); } } }; });
2. 授权策略未指定认证方案
默认策略指定了IdentityConstants.BearerScheme,但RequireAuthorization(config => config.RequireClaim("is_admin"))创建的是全新策略,未继承默认策略的认证方案配置。这会导致认证过程未使用Bearer Token方案,无法正确验证用户身份,进而引发路由匹配异常或错误返回404。
解决方法:在授权配置中显式指定认证方案:
app.MapGet("/admin/page", () => Results.Ok("admin page")) .RequireAuthorization(config => { config.RequireClaim("is_admin"); config.AddAuthenticationSchemes(IdentityConstants.BearerScheme); });
或者提前定义包含认证方案的管理员策略:
services.AddAuthorizationBuilder() .SetDefaultPolicy(new AuthorizationPolicyBuilder(IdentityConstants.BearerScheme) .RequireAuthenticatedUser() .Build()) .AddPolicy("AdminPolicy", policy => policy.RequireClaim("is_admin") .AddAuthenticationSchemes(IdentityConstants.BearerScheme));
然后端点使用该策略:
app.MapGet("/admin/page", () => Results.Ok("admin page")) .RequireAuthorization("AdminPolicy");
3. 端点注册顺序问题
如果/admin/page端点注册在路由中间件(app.UseRouting())之前,或者被更通用的路由(如MapGet("{*path}", ...))覆盖,也会导致404。确保端点注册在app.UseAuthorization()之后,且没有优先级更高的路由拦截请求。
内容的提问来源于stack exchange,提问作者JohnyL

