Azure AD场景下C# Web API如何基于Angular SPA令牌代用户访问Graph API
解决方案
你的场景适用Azure AD的OAuth 2.0 代表流(On-Behalf-Of, OBO),核心逻辑是:中间层API(你的MyAPI)拿到SPA传入的、受众为自身的用户令牌后,用这个令牌向Azure AD换发受众为Microsoft Graph的委托访问令牌,再用新令牌调用Graph接口。
1 配置Azure AD应用注册
1.1 配置MyAPI的应用注册
- 进入MyAPI的应用注册页,打开「API权限」菜单:
- 添加
Microsoft Graph的委托权限,比如你需要的User.Read,不要选应用权限 - 权限添加完成后,点击「授予管理员同意」(仅组织内部使用场景需要,多租户场景由租户管理员自行同意)
- 添加
- 打开「证书和密码」菜单:
- 新增一个客户端密码,保存好生成的密码值,后续代码配置会用到
- (可选)避免用户二次授权:打开「公开API」菜单,在
access_as_user作用域的「已知客户端应用」配置项中,添加MySPA的客户端ID
1.2 无需修改MySPA的应用注册
SPA端请求MyAPI的逻辑保持不变,还是请求scope为api://<MyAPI客户端ID>/access_as_user的访问令牌即可。
2 改造ASP.NET Core API代码
推荐使用微软官方的Microsoft.Identity.Web库处理代表流逻辑,无需手动解析令牌、处理授权请求。
2.1 安装依赖NuGet包
Install-Package Microsoft.Identity.Web Install-Package Microsoft.Identity.Web.MicrosoftGraph
2.2 修改服务注册配置
在Program.cs中添加身份验证和代表流支持:
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); // 加这一段身份验证配置 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi() // 启用代表流能力 .AddMicrosoftGraph(builder.Configuration.GetSection("Graph")) .AddInMemoryTokenCaches(); // 生产环境建议替换为分布式缓存(如Redis) // 原有其他服务注册逻辑保留 builder.Services.AddControllers(); // ... 其他配置 var app = builder.Build(); // 确保启用身份验证、授权中间件 app.UseAuthentication(); app.UseAuthorization(); // ... 原有中间件、路由配置
2.3 新增配置项
在appsettings.json中添加Azure AD和Graph的配置:
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "你的Azure AD租户ID", "ClientId": "MyAPI的客户端ID", "ClientSecret": "你之前生成的MyAPI客户端密码" }, "Graph": { "BaseUrl": "https://graph.microsoft.com/v1.0", "Scopes": "User.Read" // 多个Graph权限用空格分隔 } }
2.4 改写接口逻辑
直接注入GraphServiceClient即可调用Graph接口,无需手动处理令牌:
[Authorize] [ApiController] [Route("api/[controller]")] public class ProfileController : ControllerBase { private readonly GraphServiceClient _graphClient; public ProfileController(GraphServiceClient graphClient) { _graphClient = graphClient; } [HttpGet] public async Task<IActionResult> GetProfile() { var userProfile = await _graphClient.Me.GetAsync(); return Ok(userProfile); } }
如果你不想用Graph SDK,也可以注入ITokenAcquisition手动获取Graph访问令牌:
[Authorize] [ApiController] [Route("api/[controller]")] public class ProfileController : ControllerBase { private readonly ITokenAcquisition _tokenAcquisition; public ProfileController(ITokenAcquisition tokenAcquisition) { _tokenAcquisition = tokenAcquisition; } [HttpGet] public async Task<IActionResult> GetProfile() { // 主动用代表流获取Graph的访问令牌 string graphAccessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { "User.Read" }); // 用新令牌调用Graph接口 var result = await "https://graph.microsoft.com/v1.0/me/" .WithOAuthBearerToken(graphAccessToken) .WithHeader("Content-Type", "application/json") .GetJsonAsync(); return Ok(result); } }
3 注意事项
- 不要用应用权限调用Graph,应用权限拿到的令牌没有用户上下文,调用
/me接口会报错,不符合委托访问的需求 - 生产环境不要使用内存缓存存储令牌,建议替换为Redis等分布式缓存,避免多实例部署时重复请求令牌
- 如果需要调用多个下游API,可以在
EnableTokenAcquisitionToCallDownstreamApi后配置多个下游服务的权限
内容的提问来源于stack exchange,提问作者Kim Raaness
相关产品推荐
相关产品推荐

