You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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模式配置

测试结果

测试控制器包含三个接口:

  1. GetUserData:标记[Authorize]
  2. GetUserData2:标记[Authorize(Policy = nameof(Policies.NotBoundUser))]
  3. GetUserData3:无授权标记

用户已认证但无Claims.UserId声明时:

  • GetUserData返回403:符合预期
  • GetUserData2返回403:不符合预期(应返回数据)
  • GetUserData3返回403:符合预期

用户已认证且有Claims.UserId声明时:

  • GetUserData返回有效数据:符合预期
  • GetUserData2返回403:符合预期
  • GetUserData3返回有效数据:符合预期

问题根源

  1. 重复授权检查:同时配置了DefaultPolicy和全局OverridableAuthorizeFilter,导致标记特定策略的接口会同时执行指定策略和默认策略。当用户无Claims.UserId时,NotBoundUser策略通过,但默认策略要求该声明,最终返回403。
  2. 过滤器判断逻辑漏洞:自定义过滤器中通过em.GetType() == typeof(AuthorizeAttribute)检测显式授权,无法覆盖继承AuthorizeAttribute的自定义属性,且控制器级别的授权检测逻辑不准确,导致部分场景下默认策略被错误触发。

解决方案

方案一:使用DefaultPolicy+FallbackPolicy(推荐,符合ASP.NET Core设计)

此方案无需自定义过滤器,利用框架原生配置实现需求,同时豁免Swagger路径。

  1. 移除全局自定义过滤器:修改Mvc配置,删除options.Filters.Add(new OverridableAuthorizeFilter(Policies.Default));
  2. 配置授权策略:
services.AddAuthorization(o =>
{
    // 处理[Authorize]无指定策略的场景
    o.DefaultPolicy = Policies.Default;
    // 处理无[Authorize]标记的场景
    o.FallbackPolicy = Policies.Default;
    // 添加自定义策略
    o.AddPolicy(nameof(Policies.NotBoundUser), Policies.NotBoundUser);
});
  1. 启用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的配置,可通过修复过滤器逻辑解决冲突。

  1. 调整授权配置:保留DefaultPolicy处理[Authorize]无策略的场景,仅添加自定义策略:
services.AddAuthorization(o =>
{
    o.DefaultPolicy = Policies.Default;
    o.AddPolicy(nameof(Policies.NotBoundUser), Policies.NotBoundUser);
});
  1. 修复自定义过滤器:优化检测逻辑,支持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);
    }
}
  1. 豁免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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:24:54