混合AD场景下Web API通过MS Graph获取用户日历的认证问题
混合AD场景下仅更新Web API调用MS Graph获取日历事件的最简方案
场景回顾
- 混合Active Directory环境:本地AD与Azure AD同步,正迁移本地Exchange邮箱至Exchange Online
- 现有系统:
- 内部部署的JS SPA通过带凭据的Fetch API调用.NET Core Web API获取日历事件
- Web API仅允许特定AD组用户访问,当前以应用池身份通过EWS连接本地Exchange读取日历
- 核心需求:Web API改为代表用户调用MS Graph读取日历,且仅修改Web API代码,不改动SPA
- 已遇问题:尝试
AcquireTokenByIntegratedWindowsAuth时触发错误'Integrated Windows Auth is not supported for managed users',原因是无本地ADFS,同步到Azure AD的托管用户不支持IWA
仅修改Web API的解决方案
方案1:Kerberos约束委派 + On-Behalf-Of(OBO)流(推荐,严格代表用户身份)
此方案基于Web API已有的Windows身份验证,通过OBO流将用户身份传递给MS Graph,完全无需修改SPA。
步骤1:配置Azure AD应用注册
- 注册Web API类型的应用,记录
客户端ID和租户ID - 为应用添加委派权限:
Microsoft Graph > Calendars.Read,并完成管理员同意 - 生成客户端密钥(或使用证书),用于Web API向Azure AD验证身份
步骤2:确保Web API启用Windows身份验证
在.NET Core Web API的Program.cs中启用Windows身份验证:
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) .AddNegotiate(); builder.Services.AddAuthorization(options => { options.FallbackPolicy = options.DefaultPolicy; });
验证Web API能正确获取当前访问用户的UPN(User.Identity.Name应为同步到Azure AD的用户UPN)
步骤3:实现OBO流获取Graph令牌并调用API
在Web API中集成MSAL.NET,通过OBO流获取用户的Graph访问令牌:
using Microsoft.Identity.Client; using Microsoft.Graph; // 注册MSAL客户端 builder.Services.AddScoped<IConfidentialClientApplication>(sp => { var config = sp.GetRequiredService<IConfiguration>(); return ConfidentialClientApplicationBuilder .Create(config["AzureAd:ClientId"]) .WithTenantId(config["AzureAd:TenantId"]) .WithClientSecret(config["AzureAd:ClientSecret"]) .Build(); }); // 注册Graph服务客户端 builder.Services.AddScoped<GraphServiceClient>(async sp => { var msalClient = sp.GetRequiredService<IConfidentialClientApplication>(); var httpContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext; var userObjectId = httpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value; // 获取用户的Graph访问令牌 var oboResult = await msalClient.AcquireTokenOnBehalfOf( new[] { "https://graph.microsoft.com/Calendars.Read" }, new UserAssertion(userObjectId) ).ExecuteAsync(); return new GraphServiceClient(new DelegateAuthenticationProvider(request => { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", oboResult.AccessToken); return Task.CompletedTask; })); });
之后在API接口中直接注入GraphServiceClient调用日历接口即可:
[ApiController] [Route("[controller]")] public class CalendarController : ControllerBase { private readonly GraphServiceClient _graphClient; public CalendarController(GraphServiceClient graphClient) { _graphClient = graphClient; } [HttpGet] public async Task<IActionResult> GetCalendarEvents() { var events = await _graphClient.Me.Events.Request().GetAsync(); return Ok(events); } }
步骤4:配置本地AD的Kerberos约束委派
在本地AD中为Web API应用池的运行账户配置约束委派,允许其委派到Azure AD应用的服务主体:
- 打开应用池账户的AD属性,切换到「委派」标签
- 选择「信任此用户委派指定的服务」,点击「添加」
- 查找并添加你在Azure AD注册的应用的服务主体(可通过客户端ID定位)
- 保存配置后重启Web服务器
方案2:应用权限 + 用户筛选(简化方案,权限范围更大)
如果业务允许Web API以应用身份读取所有用户日历,再根据当前登录用户筛选,此方案无需配置Kerberos,实现更简单:
步骤1:配置Azure AD应用注册
- 注册应用后添加应用权限:
Microsoft Graph > Calendars.Read.All,完成管理员同意 - 生成客户端密钥(或证书)
步骤2:Web API使用客户端凭据流调用Graph
// 注册MSAL和Graph客户端 builder.Services.AddScoped<IConfidentialClientApplication>(sp => { var config = sp.GetRequiredService<IConfiguration>(); return ConfidentialClientApplicationBuilder .Create(config["AzureAd:ClientId"]) .WithTenantId(config["AzureAd:TenantId"]) .WithClientSecret(config["AzureAd:ClientSecret"]) .Build(); }); builder.Services.AddScoped<GraphServiceClient>(async sp => { var msalClient = sp.GetRequiredService<IConfidentialClientApplication>(); var clientCredResult = await msalClient.AcquireTokenForClient( new[] { "https://graph.microsoft.com/.default" } ).ExecuteAsync(); return new GraphServiceClient(new DelegateAuthenticationProvider(request => { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", clientCredResult.AccessToken); return Task.CompletedTask; })); });
在接口中根据当前用户UPN筛选日历:
[HttpGet] public async Task<IActionResult> GetCalendarEvents() { var userUpn = User.Identity.Name; var events = await _graphClient.Users[userUpn].Events.Request().GetAsync(); return Ok(events); }
注意:此方案需要Web API拥有读取所有用户日历的权限,适合内部可信系统,需评估安全风险。
内容的提问来源于stack exchange,提问作者datahandler
相关产品推荐
相关产品推荐

