.NET 6全局授权策略覆盖异常问题求助
ASP.NET Core 全局授权策略覆盖问题解决
问题概述
后端API遇到全局授权策略与显式授权策略冲突的问题,具体表现为标记特定策略的接口被默认策略拦截,返回不符合预期的403错误。需满足以下授权规则:
- 默认情况下,未标记
[Authorize]或标记但未指定策略的控制器/动作,使用Default授权策略验证身份 - 控制器/动作标记指定策略的
[Authorize]时,仅执行该策略,不触发默认策略 - 豁免Swagger等静态资源的授权检查
- 支持
[AllowAnonymous]跳过授权
已实现代码
授权策略定义
public static AuthorizationPolicy Default => new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .RequireClaim(Claims.UserId) .Build(); public static AuthorizationPolicy NotBoundUser => new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .RequireAssertion(ctx => !ctx.User.HasClaim(c => c.Type == Claims.UserId)) .Build();
Mvc配置
services .AddMvc(options => { options.EnableEndpointRouting = false; options.Filters.Add(new OverridableAuthorizeFilter(Policies.Default)); options.UseMaybeModelBinding(); options.UseRouteAndBodyModelBinder(); })
自定义授权过滤器
public class OverridableAuthorizeFilter : AuthorizeFilter, IAsyncAuthorizationFilter { public OverridableAuthorizeFilter(AuthorizationPolicy policy) : base(policy) { } public override async Task OnAuthorizationAsync(AuthorizationFilterContext context) { var action = context.ActionDescriptor; if (action.EndpointMetadata.Any(em => em.GetType() == typeof(AuthorizeAttribute))) { return; } var controller = action.FilterDescriptors.FirstOrDefault()?.Filter?.GetType(); if (controller != null && controller.IsDefined(typeof(AuthorizeAttribute), true)) { return; } await base.OnAuthorizationAsync(context); } }
授权与中间件配置
services.AddAuthorization(o => { o.DefaultPolicy = Policies.Default; o.AddPolicy(nameof(Policies.NotBoundUser), Policies.NotBoundUser); }); // 中间件顺序 app.UseAuthentication(); app.UseAuthorization();
JWT认证配置
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(); // Options通过Options模式配置
测试结果
测试控制器包含三个接口:
GetUserData:标记[Authorize]GetUserData2:标记[Authorize(Policy = nameof(Policies.NotBoundUser))]GetUserData3:无授权标记
用户已认证但无Claims.UserId声明时:
GetUserData返回403:符合预期GetUserData2返回403:不符合预期(应返回数据)GetUserData3返回403:符合预期
用户已认证且有Claims.UserId声明时:
GetUserData返回有效数据:符合预期GetUserData2返回403:符合预期GetUserData3返回有效数据:符合预期
问题根源
- 重复授权检查:同时配置了
DefaultPolicy和全局OverridableAuthorizeFilter,导致标记特定策略的接口会同时执行指定策略和默认策略。当用户无Claims.UserId时,NotBoundUser策略通过,但默认策略要求该声明,最终返回403。 - 过滤器判断逻辑漏洞:自定义过滤器中通过
em.GetType() == typeof(AuthorizeAttribute)检测显式授权,无法覆盖继承AuthorizeAttribute的自定义属性,且控制器级别的授权检测逻辑不准确,导致部分场景下默认策略被错误触发。
解决方案
方案一:使用DefaultPolicy+FallbackPolicy(推荐,符合ASP.NET Core设计)
此方案无需自定义过滤器,利用框架原生配置实现需求,同时豁免Swagger路径。
- 移除全局自定义过滤器:修改Mvc配置,删除
options.Filters.Add(new OverridableAuthorizeFilter(Policies.Default)); - 配置授权策略:
services.AddAuthorization(o => { // 处理[Authorize]无指定策略的场景 o.DefaultPolicy = Policies.Default; // 处理无[Authorize]标记的场景 o.FallbackPolicy = Policies.Default; // 添加自定义策略 o.AddPolicy(nameof(Policies.NotBoundUser), Policies.NotBoundUser); });
- 启用EndpointRouting并豁免Swagger:
// 修改Mvc配置,启用EndpointRouting(默认已启用,可移除EnableEndpointRouting配置) services.AddMvc(options => { options.UseMaybeModelBinding(); options.UseRouteAndBodyModelBinder(); }); // 配置端点,给Swagger路径添加AllowAnonymous app.UseEndpoints(endpoints => { endpoints.MapControllers(); // 豁免Swagger JSON接口 endpoints.MapSwagger().AllowAnonymous(); // 豁免Swagger UI页面 endpoints.MapSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }).AllowAnonymous(); });
方案二:修复自定义过滤器逻辑
若需保留禁用EndpointRouting的配置,可通过修复过滤器逻辑解决冲突。
- 调整授权配置:保留
DefaultPolicy处理[Authorize]无策略的场景,仅添加自定义策略:
services.AddAuthorization(o => { o.DefaultPolicy = Policies.Default; o.AddPolicy(nameof(Policies.NotBoundUser), Policies.NotBoundUser); });
- 修复自定义过滤器:优化检测逻辑,支持
IAuthorizeData接口并优先处理[AllowAnonymous]:
public class OverridableAuthorizeFilter : AuthorizeFilter, IAsyncAuthorizationFilter { public OverridableAuthorizeFilter(AuthorizationPolicy policy) : base(policy) { } public override async Task OnAuthorizationAsync(AuthorizationFilterContext context) { // 优先检查[AllowAnonymous],直接跳过授权 if (context.ActionDescriptor.EndpointMetadata.Any(m => m is AllowAnonymousAttribute)) { return; } // 检查动作或控制器是否有任何授权配置(包括自定义授权属性) var hasExplicitAuthorize = context.ActionDescriptor.EndpointMetadata.Any(m => m is IAuthorizeData); if (hasExplicitAuthorize) { return; } // 无显式授权,执行默认策略 await base.OnAuthorizationAsync(context); } }
- 豁免Swagger路径:添加中间件给Swagger请求注入
AllowAnonymous标记:
app.Use(async (context, next) => { if (context.Request.Path.StartsWithSegments("/swagger")) { context.Features.Set<IAllowAnonymous>(new AllowAnonymousFeature()); } await next(); }); // 实现IAllowAnonymous接口 public class AllowAnonymousFeature : IAllowAnonymous { }
验证效果
修改后重新测试:
- 用户无
Claims.UserId时,GetUserData2返回有效数据,符合预期 - Swagger可正常访问,无需授权
- 所有接口均符合预设的授权规则
内容的提问来源于stack exchange,提问作者quain
相关产品推荐
相关产品推荐

