You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 21:17:06