.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(); }); }
验证步骤
- 先临时去掉接口的
[Authorize(Roles = "xxxRole")]属性,仅保留[Authorize],携带Bearer token请求接口,确认返回200,且context.User.Claims中存在type为role的声明。 - 确认JWT中
role字段的值和你接口上Roles参数的值完全一致,注意大小写敏感。你提供的示例JWT中role值为tRole,如果接口上写的是xxxRole会直接校验失败。
内容的提问来源于stack exchange,提问作者microsugar
相关产品推荐
相关产品推荐

