.NET7迁移Microsoft Graph后MSI身份验证遇403权限错误求助
问题:迁移Microsoft Graph代码到.NET7后出现403权限不足错误
原有工作代码(ASP.NET Core 3.1 + Microsoft.Graph 4.0.0)
private async Task<GraphServiceClient> GetGraphServiceClientUsingMSIAsync() { var accessToken = await GetAccessTokenForMicrosoftGraphUsingMSIAsync(); var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider((req) => { req.Headers.Authorization = new AuthenticationHeaderValue(C.Constants.Bearer, accessToken); return Task.CompletedTask; })); return graphServiceClient; }
private async Task<User> GetUserFromMicrosoftGraphAsync(string email, User user, string cacheKey) { var queryOptions = new List<QueryOption>{new QueryOption("$select", "id,userPrincipalName,mail,displayName,surname,givenname,officeLocation,usertype"), new QueryOption("$filter", $"mail eq '{email}' or userPrincipalName eq '{email}'")}; var graphClient = await GetGraphServiceClientUsingMSIAsync(); var userDetails = await graphClient.Users.Request(queryOptions).GetAsync(); user = userDetails.FirstOrDefault(x => x.UserType != Constants.Guest); if (user != null) { user.UserPrincipalName = (user.Mail ??= user.UserPrincipalName).ToLower(); user.Mail = user.Mail.ToLower(); _cacheProvider.Set(cacheKey, user, C.CacheGroups.Graph); } return user; }
迁移后的代码(.NET7 + Microsoft.Graph 5.12.0)
private async Task<GraphServiceClient> GetGraphServiceClientUsingMSIAsync() { if (_graphServiceClient != null) return _graphServiceClient; string[] scopes = new[]{"https://graph.microsoft.com/.default"}; var chainedTokenCredential = GetChainedTokenCredentials(); _graphServiceClient = new GraphServiceClient(chainedTokenCredential, scopes); return _graphServiceClient; } private ChainedTokenCredential GetChainedTokenCredentials() { if (!_environment.IsDevelopment()) { return new ChainedTokenCredential(new ManagedIdentityCredential()); } else // dev env { var tenantId = _configuration["xxxxxx"]; var clientId = _configuration["yyyyyy"]; var clientSecret = _configuration["aaaaa"]; var options = new TokenCredentialOptions{AuthorityHost = AzureAuthorityHosts.AzurePublicCloud}; var devClientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options); var chainedTokenCredential = new ChainedTokenCredential(devClientSecretCredential); return chainedTokenCredential; } } private async Task<User> GetUserFromMicrosoftGraphAsync(string email, User user, string cacheKey) { var graphClient = await GetGraphServiceClientUsingMSIAsync(); var filter = $"mail eq '{email}' or userPrincipalName eq '{email}'"; var result = await graphClient.Users[filter].GetAsync((requestConfiguration) => { requestConfiguration.QueryParameters.Select = new string[]{"id", "userPrincipalName", "mail", "displayName", "surname", "givenname", "officeLocation", "usertype"}; }); user = result; if (user is not null) { user.UserPrincipalName = (user.Mail ??= user.UserPrincipalName).ToLower(); user.Mail = user.Mail.ToLower(); _cacheProvider.Set(cacheKey, user, CacheGroups.Graph); } return user; } public async Task<User> GetUserAsync(string email) { var cacheKey = FormatKey(CacheKeys.GraphUser, email); User user = _cacheProvider.Get<User>(cacheKey, CacheGroups.Graph); if (user is null) { user = await GetUserFromMicrosoftGraphAsync(email, user, cacheKey); } return user; }
错误详情
"InnerExceptions":[{"HResult":-2146233088,"Message":"Exception of type 'Microsoft.Graph.Models.ODataErrors.ODataError' was thrown.","Source":"Microsoft.Kiota.Http.HttpClientLibrary","TargetSite":"Void MoveNext()","AdditionalData":{},"BackingStore":{"ReturnOnlyChangedValues":false,"InitializationCompleted":true},"Error":{"AdditionalData":{"innerError":{"ValueKind":"Object","$type":"JsonElement"}},"BackingStore":{"ReturnOnlyChangedValues":false,"InitializationCompleted":true},"Code":"Authorization_RequestDenied","Details":null,"Innererror":null,"Message":"Insufficient privileges to complete the operation.","Target":null},"ResponseStatusCode":403,"Type":"Microsoft.Graph.Models.ODataErrors.ODataError"}]
解决方法
- 配置本地开发用Azure AD应用的权限:本地开发使用的ClientSecretCredential对应的Azure AD应用,需要添加User.Read.All或Directory.Read.All的应用权限(不是委托权限),并完成管理员同意。原有MSI模式的权限不共享给本地用的客户端ID,需单独配置。
- 修正Graph查询语法:迁移后的代码错误地将filter条件放入
Users[]索引器(该索引器用于通过用户ID获取单个用户),正确的查询写法如下:private async Task<User> GetUserFromMicrosoftGraphAsync(string email, User user, string cacheKey) { var graphClient = await GetGraphServiceClientUsingMSIAsync(); var result = await graphClient.Users.GetAsync(requestConfiguration => { requestConfiguration.QueryParameters.Select = new string[]{"id", "userPrincipalName", "mail", "displayName", "surname", "givenname", "officeLocation", "usertype"}; requestConfiguration.QueryParameters.Filter = $"mail eq '{email}' or userPrincipalName eq '{email}'"; }); // 恢复原有排除Guest用户的逻辑 user = result.Value?.FirstOrDefault(x => x.UserType != Constants.Guest); if (user is not null) { user.UserPrincipalName = (user.Mail ??= user.UserPrincipalName).ToLower(); user.Mail = user.Mail.ToLower(); _cacheProvider.Set(cacheKey, user, CacheGroups.Graph); } return user; } - 验证权限有效性:可以通过客户端凭证流获取token后,直接调用Graph API端点
GET https://graph.microsoft.com/v1.0/users?$select=id,userPrincipalName,...&$filter=mail eq '你的测试邮箱',确认是否能正常返回用户数据,排查权限是否配置到位。 - 补全用户过滤逻辑:迁移后的代码丢失了原有排除Guest用户的逻辑,需恢复
FirstOrDefault(x => x.UserType != Constants.Guest)的判断,避免返回不符合要求的用户。
内容的提问来源于stack exchange,提问作者santosh kumar patro
相关产品推荐
相关产品推荐

