使用Microsoft.Identity.Web的RequiredScope验证控制器范围失效问题排查
解决AD B2C环境下[RequiredScope]特性不生效的问题
问题描述
在基于.NET微服务架构的eShopOnContainers示例中,将认证方式从IdentityServer4切换为AD B2C后,基础认证功能正常。但在控制器上添加[Authorize]和[RequiredScope]特性后,即便把指定的scope改为令牌中不存在的内容(如abc123),请求依然能正常通过,[RequiredScope]未起到校验作用。
初始认证配置代码:
builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.MetadataAddress = $"<iss URL>.well-known/openid-configuration?p=<tfp>"; options.Audience = "<aud GUID>"; });
问题原因
默认的JWT Bearer认证仅会验证令牌的签名、过期时间、受众(Audience)等基础合法性,不会自动关联[RequiredScope]特性进行scope校验,因此需要手动配置校验逻辑。
解决方案
方案一:自定义令牌验证事件处理scope校验
修改JWT Bearer配置,添加OnTokenValidated事件,在令牌验证通过后手动校验scope是否匹配[RequiredScope]的要求:
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Security.Claims; builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.MetadataAddress = $"<iss URL>.well-known/openid-configuration?p=<tfp>"; options.Audience = "<aud GUID>"; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateAudience = true, ValidateIssuer = true, ValidateLifetime = true, // 适配AD B2C的声明字段名称 NameClaimType = ClaimTypes.Name, RoleClaimType = ClaimTypes.Role }; // 新增令牌验证后的scope校验逻辑 options.Events = new JwtBearerEvents { OnTokenValidated = context => { // 获取当前端点要求的scope var requiredScopeAttr = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<RequiredScopeAttribute>(); if (requiredScopeAttr?.RequiredScopes == null || !requiredScopeAttr.RequiredScopes.Any()) { return Task.CompletedTask; } // 从令牌中提取已授权的scope(AD B2C通常用"scp"字段存储scope) var tokenScopes = context.Principal?.FindAll("scp")?.Select(c => c.Value) ?? Enumerable.Empty<string>(); var tokenScopeSet = new HashSet<string>(tokenScopes, StringComparer.OrdinalIgnoreCase); // 校验是否包含所有要求的scope foreach (var scope in requiredScopeAttr.RequiredScopes) { if (!tokenScopeSet.Contains(scope)) { context.Fail($"缺少必要的scope: {scope}"); break; } } return Task.CompletedTask; } }; });
方案二:使用授权策略替代[RequiredScope]
如果不想自定义事件处理,可以通过注册授权策略来实现scope校验,控制器上改用策略名称:
// 注册授权策略 builder.Services.AddAuthorization(options => { options.AddPolicy("ReadScope", policy => { policy.RequireAuthenticatedUser(); // 要求令牌包含"read" scope(AD B2C的scope字段为"scp") policy.RequireClaim("scp", "read"); }); // 可添加多个不同scope的策略 options.AddPolicy("WriteScope", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scp", "write"); }); });
控制器上使用:
[Authorize(Policy = "ReadScope")] public class ProductsController : ControllerBase { // ... }
注意事项
- 确认AD B2C返回的令牌中,scope存储的字段名称:客户端凭证流通常用
scp,授权码流可能用scope,需根据实际令牌内容调整代码中的Claim名称。 - 若使用.NET 7及以上版本,可直接使用
policy.RequireScope("read")方法替代RequireClaim,该方法会自动适配不同的scope声明字段。
内容的提问来源于stack exchange,提问作者Ogglas
相关产品推荐
相关产品推荐

