如何通过Graph和Microsoft Identity Web为API同时配置应用与委托权限
问题描述
我正在开发的应用需要访问用户不一定具备权限的OneDrive组,需要使用应用权限(具体为Sites.Selected权限),同时应用仍需支持代表用户执行操作。
为尽可能收窄应用权限范围以保障API安全性,希望能够同时使用委托权限与应用权限。
已尝试操作
此前查阅的资料显示需要创建两个独立的Graph Client实例分别获取对应令牌,相关内容发布于2018年,当前技术栈已有较多更新。根据公开的Microsoft Identity Web官方文档说明,可使用同一个Graph实例,通过.WithAppOnly()和.WithScopes()方法指定调用时使用的权限类型。
当前使用Microsoft.Identity.Web库,通过.AddMicrosoftGraph方法注入服务,同时配置.WithAppOnly和.WithScopes,但应用权限始终无法生效:.WithScopes相关调用正常,但调用.WithAppOnly时抛出如下错误:
System.Collections.Generic.KeyNotFoundException: The given key 'Microsoft.Graph.AuthenticationHandlerOption' was not present in the dictionary. at System.Collections.Generic.Dictionary`2.get_Item(TKey key) at Microsoft.Identity.Web.BaseRequestExtensions.SetParameter[T](T baseRequest, Action`1 action)
Azure应用权限配置

SharePoint权限检查结果
{ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites('SITE_ID')/permissions/$entity", "id": "PERMISSION ID", "roles": [ "write" ], "grantedToIdentities": [ { "application": { "displayName": "MY API", "id": "API CLIENT ID" } } ] }
相关代码
Startup.cs
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd") .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftGraph(Configuration.GetSection("Graph")) //.AddMicrosoftGraphAppOnly(authenticationProvider => new GraphServiceClient(authenticationProvider)) .AddInMemoryTokenCaches();
appsettings.json
"AzureAd": { "Instance": "https://login.microsoftonline.com/", "ClientId": "{ClientId}", "TenantId": "{TenantId}", "CallbackPath": "/signin-oidc", "Audience": "{ClientId}", "ClientSecret": "{ClientSecret}", "ClientCertificates": [ ] }, "Graph": { "BaseUrl": "https://graph.microsoft.com/v1.0", "Scopes": "user.read", "DefaultScope": "https://graph.microsoft.com/.default" }
未添加.WithAppOnly的Graph调用可正常执行,但此时采用用户身份验证Graph Client,若用户无对应OneDrive组访问权限,调用会失败:
WorkbookSessionInfo res = await _graphServiceClient.Groups[_groupId].Drive.Items[productStructureCalculator.CalculatorId].Workbook .CreateSession(persistChanges) .Request() .WithAppOnly() .Header("Prefer", "respond-async") .PostAsync();
解决方案
报错原因
默认通过AddMicrosoftGraph注入的GraphServiceClient仅配置了委托权限场景的认证处理选项,缺少应用权限调用所需的AuthenticationHandlerOption配置,调用.WithAppOnly()时找不到对应配置项就会抛出字典找不到Key的异常。
另外微软官方文档提到的单实例切换权限类型的方案,要求Microsoft.Identity.Web和Microsoft.Graph的Nuget包版本匹配,低版本不支持该特性。
推荐方案:双实例注册(稳定性最高)
无需纠结单实例切换权限的兼容问题,分别注册委托权限、应用权限两个独立的GraphServiceClient实例即可,步骤如下:
- 修改Startup.cs注册逻辑:
// 注册代表用户调用的委托权限GraphClient(默认注入) services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd") .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftGraph(Configuration.GetSection("Graph")) .AddInMemoryTokenCaches(); // 注册应用权限专用的GraphClient,使用键控服务避免冲突 services.AddKeyedSingleton<GraphServiceClient>("AppOnlyGraph", (sp, _) => { var tokenAcquisition = sp.GetRequiredService<ITokenAcquisition>(); var authProvider = new BaseBearerTokenAuthenticationProvider(async (request) => { var token = await tokenAcquisition.GetAccessTokenForAppAsync("https://graph.microsoft.com/.default"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); }); return new GraphServiceClient(authProvider); });
- 业务代码中按需注入对应实例使用:
// 构造函数注入两个实例 public YourService(GraphServiceClient userGraphClient, [FromKeyedServices("AppOnlyGraph")] GraphServiceClient appOnlyGraphClient) { _userGraphClient = userGraphClient; // 委托权限,代表用户调用 _appOnlyGraphClient = appOnlyGraphClient; // 应用权限,无需用户权限 } // 应用权限调用无需加.WithAppOnly() WorkbookSessionInfo res = await _appOnlyGraphClient.Groups[_groupId].Drive.Items[productStructureCalculator.CalculatorId].Workbook .CreateSession(persistChanges) .Request() .Header("Prefer", "respond-async") .PostAsync();
可选方案:单实例切换权限
如果坚持使用单实例,将Microsoft.Identity.Web和Microsoft.Graph Nuget包升级到最新稳定版,修改AddMicrosoftGraph配置即可:
services.AddMicrosoftGraph(options => { options.Scopes = "user.read"; options.DefaultScope = "https://graph.microsoft.com/.default"; }, pipeline => { // 明确添加认证处理选项 pipeline.AddAuthenticationHandler(); });
升级后即可正常使用.WithAppOnly()、.WithScopes()切换权限类型。
内容的提问来源于stack exchange,提问作者Wynand Coetzer

