You need to enable JavaScript to run this app. .NET Core 3.1 混合Cookie与Bearer认证时角色授权失效问题
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core 3.1 混合Cookie与Bearer认证时角色授权失效问题

问题根因

  • 核心错误是重复调用了services.AddAuthentication()方法,第二次调用的配置会直接覆盖第一次的JWT相关配置,导致Bearer认证逻辑完全未被注册。
  • 可选优化:默认的多方案授权策略在部分场景下优先级逻辑不清晰,建议新增动态策略方案根据请求头自动选择认证方式。
  • 中间件顺序存在隐性问题:自定义401跳转中间件的位置建议调整到UseAuthorization之后,避免授权逻辑未执行就提前处理响应。

修正后的配置代码

ConfigureServices 部分

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    // 仅调用一次AddAuthentication,合并所有认证配置
    services.AddAuthentication(options =>
    {
        // 配置默认策略方案,动态选择认证方式
        options.DefaultScheme = "DynamicAuthScheme";
        options.DefaultChallengeScheme = "DynamicAuthScheme";
    })
    // 添加动态方案选择逻辑
    .AddPolicyScheme("DynamicAuthScheme", "Dynamic authorization scheme", options =>
    {
        options.ForwardDefaultSelector = context =>
        {
            // 请求带Bearer头则用JWT认证,否则用Cookie认证
            var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
            if (authHeader?.StartsWith("Bearer ") == true)
            {
                return JwtBearerDefaults.AuthenticationScheme;
            }
            return CookieAuthenticationDefaults.AuthenticationScheme;
        };
    })
    // 配置JWT Bearer认证
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.Authority = Configuration["auth:oidc:AuthBaseUri"];
        options.Audience = Configuration["auth:oidc:Scopes"];

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            NameClaimType = "name",
            RoleClaimType = "role",
        };

        options.SaveToken = true;
    })
    // 配置Cookie认证
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    // 配置OIDC登录
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {
        options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;

        options.Authority = Configuration["auth:oidc:AuthBaseUri"];
        options.ClientId = Configuration["auth:oidc:ClientId"];
        options.ClientSecret = Configuration["auth:oidc:ClientSecret"];

        options.Scope.Clear();
        options.Scope.Add("openid");
        options.Scope.Add("roles");
        options.Scope.Add("profile");
        options.Scope.Add(Configuration["auth:oidc:Scopes"]);

        options.GetClaimsFromUserInfoEndpoint = true;
        options.ClaimActions.MapJsonKey("role", "role", "role");

        options.UseTokenLifetime = true;

        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };

        options.SaveTokens = true;
        options.ResponseType = OpenIdConnectResponseType.Code;
    });

    services.AddAuthorization(options =>
    {
        var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
            CookieAuthenticationDefaults.AuthenticationScheme, 
            JwtBearerDefaults.AuthenticationScheme);
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
        options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
    });
}

Configure 部分

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    // 认证→授权→自定义401处理,顺序不能乱
    app.UseAuthentication();
    app.UseAuthorization();

    // 自定义401跳转中间件移到UseAuthorization之后
    app.Use(async (context, next) =>
    {
        await next();
        var bearerAuth = context.Request.Headers["Authorization"]
            .FirstOrDefault()?.StartsWith("Bearer ") ?? false;
        if (context.Response.StatusCode == 401
            && !context.User.Identity.IsAuthenticated
            && !bearerAuth)
        {
            await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme);
        }
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

验证步骤

  1. 先临时去掉接口的[Authorize(Roles = "xxxRole")]属性,仅保留[Authorize],携带Bearer token请求接口,确认返回200,且context.User.Claims中存在type为role的声明。
  2. 确认JWT中role字段的值和你接口上Roles参数的值完全一致,注意大小写敏感。你提供的示例JWT中role值为tRole,如果接口上写的是xxxRole会直接校验失败。

内容的提问来源于stack exchange,提问作者microsugar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.20 02:54:04