ASP.NET Core 6 Web API AAD授权调用Graph API报错咨询
问题根因
AADSTS28000是Azure AD的强制规则导致的:单次访问令牌请求仅允许对应一个目标资源(Audience),无法在同一次令牌申请中同时请求自定义Web API(资源标识为api://<Web API客户端ID>)和Microsoft Graph(资源标识为https://graph.microsoft.com)的权限,两类资源的令牌必须分开获取。
你之前在「公开API」板块添加客户端应用的操作仅用于实现权限预授权、跳过管理员同意流程,无法解决跨资源申请令牌的限制。
正确实现流程
你当前的架构是「公共客户端调用自定义Web API,自定义Web API再调用Microsoft Graph」,属于典型的代理调用场景,不能靠客户端一次性申请跨资源令牌实现,正确链路如下:
- 客户端仅申请访问自定义Web API的令牌,scope参数只包含自定义API的
EmployeeRecord.Read和openid、offline_access、profile等基础OpenID Connect scope,用这个令牌调用自定义Web API即可,这部分你已经跑通,不需要改动。 - 自定义Web API收到请求中的Bearer令牌后,不直接用该令牌调用Graph,而是将该令牌作为用户身份凭证,通过OBO(On-Behalf-Of)流单独向AAD申请访问Microsoft Graph的专属令牌。
- Web API拿到Graph专属令牌后,再携带该令牌调用Graph接口,就不会触发MsalUIRequiredException异常。
具体落地步骤
你创建项目时已经加了--calls-graph true参数,默认已经引入了Microsoft.Identity.Web和Graph SDK相关依赖,只需要补全配置和调用逻辑即可:
配置调整
- 权限配置修正:将Microsoft Graph的
User.Read权限从公共客户端应用注册,转移到你的Web API应用注册中——因为实际调用Graph的主体是Web API,不是公共客户端,公共客户端不需要持有Graph权限。给该权限配置租户级管理员同意,避免调用时触发同意提示。 - 确认appsettings.json配置正确:
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "company.onmicrosoft.com", "TenantId": "<替换为你的租户ID>", "ClientId": "<替换为你的Web API客户端ID>", "ClientSecret": "<替换为你的Web API客户端密钥,使用证书认证则替换为对应证书配置>", "Scopes": "EmployeeRecord.Read" }, "GraphApi": { "BaseUrl": "https://graph.microsoft.com/v1.0", "Scopes": "User.Read" } }
- Program.cs中补全服务注入,开启OBO流和Graph客户端支持:
using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); // 注册JWT认证,开启下游API调用的令牌获取能力,注入Graph客户端 builder.Services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftGraph(builder.Configuration.GetSection("GraphApi")) .AddInMemoryTokenCaches(); builder.Services.AddControllers(); // 其他原有服务注入逻辑保持不变 var app = builder.Build(); // 中间件逻辑保持原有配置即可,确保认证授权中间件顺序正确 app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
接口调用示例
在需要调用Graph的控制器中直接注入GraphServiceClient即可,SDK底层会自动完成OBO流令牌兑换、令牌缓存的全流程,不需要手动编写令牌申请逻辑:
using Microsoft.AspNetCore.Authorization; using Microsoft.Graph; using Microsoft.Identity.Web.Resource; [ApiController] [Route("api/employee")] [RequiredScope("EmployeeRecord.Read")] // 校验访问当前接口的令牌必须包含该scope public class EmployeeController : ControllerBase { private readonly GraphServiceClient _graphClient; public EmployeeController(GraphServiceClient graphClient) { _graphClient = graphClient; } [HttpGet("current-user")] public async Task<IActionResult> GetCurrentUser() { // 自动用当前请求的用户令牌兑换Graph访问令牌后发起调用 var user = await _graphClient.Me.GetAsync(); return Ok(new { DisplayName = user.DisplayName, Email = user.Mail }); } }
避坑说明
- 绝对不要在客户端的令牌请求中同时拼接两个不同资源的scope,这是AAD的硬性限制,没有绕过方式。
- 不要让客户端直接拿Graph令牌调用你的自定义Web API,两种令牌的Audience校验规则不同,必然会返回401未授权。
- 开发环境如果用InMemory令牌缓存,服务重启后缓存会清空,生产环境建议替换为分布式缓存(如Redis)存储令牌。
内容的提问来源于stack exchange,提问作者ezG
相关产品推荐
相关产品推荐

