ASP.NET Core 5 Azure AD JWT认证context.User.Claims为空问题
- 运行环境:ASP.NET Core 5 Web API,对接Azure服务,已在所属租户Azure AD中完成应用注册,授权相关业务逻辑编写在
Authorization授权处理器中,目标是将JWT令牌内的声明提取到context.User.Claims。 - 异常表现:手动使用
JwtSecurityTokenHandler读取请求头context.Request.Headers["Authorization"]携带的JWT令牌时,可以正常解析得到全部声明,但context.User.Claims集合始终为空。 - 异常令牌的核心字段取值:
[aud, https://graph.microsoft.com] [iss, https://sts.windows.net/[tenant-id]/]
- 现有JWT认证配置(位于
Startup.ConfigureServices()方法中):
services.AddAuthentication(sharedOptions => { sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(option => { option.Audience = audience; option.Authority = authority; option.TokenValidationParameters = new TokenValidationParameters { ClockSkew = TimeSpan.FromHours(1), ValidateLifetime = true, ValidateIssuer = true, ValidIssuer = authority, ValidateAudience = true, ValidAudience = audience }; });
- 直接将JWT中解析得到的
iss、aud值填入authority和audience配置项 - 测试v2端点配置组合:
authority(令牌颁发者):https://login.microsoftonline.com/[tenant-id]/v2.0audience(令牌受众):app://[client-id]
- 所有配置组合测试后,
context.User.Claims仍为空,需要确认audience与authority参数的正确配置方式。
context.User.Claims为空的核心原因是JWT Bearer认证中间件没有完成令牌校验通过,不会将声明注入到用户上下文中。你当前拿到的JWT是颁发给Microsoft Graph服务的访问令牌,不是颁发给你自定义API应用的令牌,无论怎么调整配置参数,中间件都会因为受众校验不通过拒绝认证,手动解析令牌不经过校验逻辑,所以能读到声明。
1. 先修正令牌获取逻辑
访问令牌的aud(受众)字段必须匹配你自己注册的API应用,不能为https://graph.microsoft.com。客户端申请令牌时,scope参数需要填写你自己的API在应用注册时暴露的权限范围,格式通常为api://<你的API应用客户端ID>/.default或自定义作用域,不能填写Microsoft Graph的权限范围。
2. authority 参数配置
你当前拿到的令牌iss字段为https://sts.windows.net/[tenant-id]/,属于Azure AD v1端点颁发的令牌,对应的authority必须配置为完全一致的https://sts.windows.net/[替换为实际租户ID]/,不要混用v2.0端点地址。
如果后续切换到v2端点申请令牌,令牌iss会变为https://login.microsoftonline.com/[tenant-id]/v2.0,此时再将authority替换为对应v2地址即可,两个版本的端点和令牌不能交叉配置。
3. audience 参数配置
audience必须和最终拿到的、颁发给你自己API的令牌中aud字段完全一致:
- 针对v1端点颁发的令牌,
aud取值为你API应用注册时配置的应用ID URI,常规格式为api://<你的API应用客户端ID> - 禁止使用
https://graph.microsoft.com作为audience取值,该值对应的受保护资源是Microsoft Graph,不是你的自定义API。
4. 修正后的完整配置
你原有配置重复在AddJwtBearer根级别和TokenValidationParameters中设置了同名字段,保留一套配置即可,避免取值冲突。可参考以下配置,同时开启认证失败日志方便后续排查:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ClockSkew = TimeSpan.FromHours(1), ValidateLifetime = true, ValidateIssuer = true, // 替换为实际租户ID,和令牌iss字段完全匹配 ValidIssuer = "https://sts.windows.net/[替换为你的租户ID]/", ValidateAudience = true, // 替换为你自己API的应用ID URI,和令牌aud字段完全匹配 ValidAudience = "api://[替换为你的API应用客户端ID]", // 如果存在多个允许的受众,可改用ValidAudiences传入数组 // ValidAudiences = new[] { "api://[client-id]", "其他合法aud值" } }; // 可选:注册认证失败事件,打印错误信息快速定位校验问题 options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { var failReason = context.Exception.Message; // 此处可添加日志逻辑记录失败原因 return Task.CompletedTask; } }; });
配置完成后,确保客户端携带的是颁发给你自身API的合法令牌,JWT Bearer中间件校验通过后会自动将令牌内的声明填充到context.User.Claims中。
内容的提问来源于stack exchange,提问作者Cowborg

