调用Azure AD Web API遇403授权失败:token含scp却不符角色要求
问题:调用受保护的Web API时出现HTTP 403错误
问题背景
运行微软Web API示例项目,完成官方文档所有配置步骤后,调用API仍收到HTTP 403错误。
排查细节
- 开启全量日志追踪,配置如下:
"Logging": { "LogLevel": { "Default": "Trace", "Microsoft.AspNetCore": "Trace" } }
- 日志显示授权失败:
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. These requirements were not met:
RolesAuthorizationRequirement:User.IsInRole must be true for one of the following roles: (Forecast.Read)
- 通过jwt.io验证token,确认包含
scp声明,值为Forecast.Read - 移除代码中的
.RequireAuthorization("AuthZPolicy")后,API可正常调用
相关代码片段
app.MapGet("/weatherforecast", () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName("weatherForecast") .RequireAuthorization("AuthZPolicy"); // Protect this endpoint with the AuthZPolicy
原因及解决方法
核心原因
你的AuthZPolicy授权策略被配置为基于角色的校验规则,但token中携带的是OAuth2范围(Scope)声明scp。ASP.NET Core默认不会将scp声明识别为角色,因此触发RolesAuthorizationRequirement校验失败。
解决方式
方式1:修改授权策略为范围校验
直接将策略从角色要求改为范围要求,匹配token中的scp声明:
builder.Services.AddAuthorization(options => { options.AddPolicy("AuthZPolicy", policy => policy.RequireScope("Forecast.Read")); });
方式2:将scp声明映射为角色
如果需要保留角色校验逻辑,可以通过自定义Claims转换,把token中的scp值添加为角色声明:
// 注册认证服务时添加自定义ClaimsPrincipalFactory builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) .AddClaimsPrincipalFactory<ScopeToRoleClaimsFactory>(); // 自定义Claims转换类 public class ScopeToRoleClaimsFactory : MicrosoftIdentityClaimsPrincipalFactory { public ScopeToRoleClaimsFactory(IAzureADOptionsMonitor<MicrosoftIdentityOptions> optionsAccessor, ILogger<MicrosoftIdentityClaimsPrincipalFactory> logger) : base(optionsAccessor, logger) { } protected override async Task<ClaimsPrincipal> GenerateClaimsPrincipalAsync(TokenValidatedContext context) { var principal = await base.GenerateClaimsPrincipalAsync(context); var scopeClaim = context.SecurityToken.Claims.FirstOrDefault(c => c.Type == "scp")?.Value; if (!string.IsNullOrEmpty(scopeClaim)) { foreach (var scope in scopeClaim.Split(' ')) { ((ClaimsIdentity)principal.Identity).AddClaim(new Claim(ClaimTypes.Role, scope)); } } return principal; } }
内容的提问来源于stack exchange,提问作者Rob
相关产品推荐
相关产品推荐

