使用 HttpClient 向 Microsoft Graph 请求用户列表时出现未经授权的问题

UnAuthorized issue when request user list to Microsoft Graph by using HttpClient

我正在尝试通过以下方式从 Microsoft Graph 获取有关用户的信息 https://graph.microsoft.com/v1.0/users

它正在返回 401 - Unauthorized:

{
  "error": {
    "code": "InvalidAuthenticationToken",
    "message": "Access token validation failure. Invalid audience.",
    "innerError": {
      "request-id": "3157d513-6f31-4d2d-a3d7-a97eed7207ba",
      "date": "2019-12-11T05:39:02"
    }
  }
}

我的代码:

AuthenticationContext authContext =
    new AuthenticationContext(string.Format(CultureInfo.InvariantCulture,
        "https://login.microsoftonline.com/{0}", "my-domain name"));

ClientCredential clientCred =
    new ClientCredential("Client-id", "Client-Secret-id");

AuthenticationResult authenticationResult = authContext
    .AcquireTokenAsync("https://graph.windows.net", clientCred).Result;

var token = authenticationResult.AccessToken;

var client = new HttpClient();
var uri = "https://graph.microsoft.com/v1.0/me/";

client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(token);
var response = await client.GetAsync(uri);

我哪里做错了?为什么我没有获得正确的访问令牌?谁能帮我使用 MS Graph?

您使用了错误的资源,您需要获取 Microsoft Graph 的令牌而不是 AAD Graph, 应该是 https://graph.microsoft.com,而不是 https://graph.windows.net.

AuthenticationResult authenticationResult = authContext.AcquireTokenAsync("https://graph.microsoft.com",
                      clientCred).Result;

更新:

确保授予 User.Read.All 应用程序权限。

然后尝试下面的代码,它在我这边有效。

using System;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            string _authString = "https://login.microsoftonline.com/xxxxxx.onmicrosoft.com";
            string _clientId = "<client-id>";
            string _clientSecret = "<client-secret>";
            AuthenticationContext authenticationContext = new AuthenticationContext(_authString, false);
            ClientCredential clientCred = new ClientCredential(_clientId, _clientSecret);
            AuthenticationResult authenticationResult;
            authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCred).GetAwaiter().GetResult();
            Console.WriteLine(authenticationResult.AccessToken);

            var token = authenticationResult.AccessToken;

            var client = new HttpClient();
            var uri = "https://graph.microsoft.com/v1.0/users";

            client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
            client.DefaultRequestHeaders.Accept.Clear();
            //GET Method  
            HttpResponseMessage response = client.GetAsync(uri).GetAwaiter().GetResult();
            Console.WriteLine(response.Content.ReadAsStringAsync().Result.ToString());
        }
    }
}

我认为如果您调用 Microsoft Graph,资源需要是 https://graph.microsoft.com 而不是 AAD Graph (graph.windows.net)。您可以尝试在 AcquireTokenAsync 通话中更改它吗?

有两个问题:

  1. 错误的资源,资源应该是https://graph.microsoft.com。并确认您已在 Azure AD 门户

  2. 中授予正确的 Microsoft Graph 权限
  3. 您正在使用客户端凭据流,就像在没有用户的情况下使用 AcquireTokenAsync(String, ClientCredential) 方法一样,因此 https://graph.microsoft.com/v1.0/me/ 将不起作用,因为其中没有用户。请改用 GET /users/{id | userPrincipalName}。此外,您应该在 Azure 门户中授予 Application Permission,因为您使用的是 M2M 流程。

    Permissions (from least to most privileged) :

    Application :User.Read.All, User.ReadWrite.All, Directory.Read.All, Directory.ReadWrite.All

确保您在调用 Graph API 时使用的帐户具有必需的权限。当您调用 GET 调用时,

应设置以下权限。

更多关于权限的信息:https://docs.microsoft.com/en-us/graph/permissions-reference

发布的错误清楚地指出您用来调用 GRAPH API 的帐户未经授权。正确设置权限后,将生成访问令牌并将针对您的应用程序进行身份验证。

编辑:尝试使用以下代码获取有效的访问令牌。


        static string AppID = "<Your Application ID>";
        static string APPKey = "<Your Application Key>";        
        static string tenantId = "<Your ORG Tenant ID>";
        static string RedirectURI = "<Your Application's custom Redirect URI>";
        static string GraphApi = "https://graph.microsoft.com/v1.0/"


public static IAuthenticationProvider CreateAuthorizationProvider()
        {            
            var authority = $"https://login.microsoftonline.com/{tenantId}/v2.0";

            List<string> scopes = new List<string>();
            scopes.Add("https://graph.microsoft.com/.default");

            var cca = ConfidentialClientApplicationBuilder.Create(AppID)
                                                    .WithAuthority(authority)
                                                    .WithRedirectUri(RedirectURI)
                                                    .WithClientSecret(APPKey)
                                                    .Build();
            return new MsalAuthenticationProvider(cca, scopes.ToArray());
        }
        public static HttpClient GetAuthenticatedHTTPClient()
        {
            var authenticationProvider = CreateAuthorizationProvider();
            _httpClient = new HttpClient(new AuthHandler(authenticationProvider, new HttpClientHandler()));
            return _httpClient;
        }

private static async Task<User> GetADUserInfo(HttpClient client,string email)
        {
            User user = new User();           
            client = GetAuthenticatedHTTPClient();
            client.BaseAddress = new Uri(GraphApi);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            WriteToConsole("Call Graph API :: retrieving AD Info for the employee ::" + email);
            using (client)
            {
                try
                {
                    HttpResponseMessage res = await client.GetAsync("users/" + email);
                    res.EnsureSuccessStatusCode();
                    if (res.IsSuccessStatusCode)
                    {                        
                        user = await res.Content.ReadAsAsync<User>();
                        WriteToConsole("Call Graph API :: Call Success for employee ::" + email);

                    }                    
                }                
                catch (Exception ex)
                {

                    LogError(ex, "Error in Getting AD User info via Graph API");
                    return null;
                }
                return user;
            }
        }

以上代码使用MSALAuthentication,使用下面的代码:

public class MsalAuthenticationProvider : IAuthenticationProvider
    {
        private IConfidentialClientApplication _clientApplication;
        private string[] _scopes;

        public MsalAuthenticationProvider(IConfidentialClientApplication clientApplication, string[] scopes)
        {
            _clientApplication = clientApplication;
            _scopes = scopes;
        }

        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
        {
            var token = await GetTokenAsync();
            request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
        }

        public async Task<string> GetTokenAsync()
        {
            AuthenticationResult authResult = null;
            authResult = await _clientApplication.AcquireTokenForClient(_scopes).ExecuteAsync();
            return authResult.AccessToken;
        }
    }

AuthHandler Class :

public class AuthHandler : DelegatingHandler
        {
            private IAuthenticationProvider _authenticationProvider;

            public AuthHandler(IAuthenticationProvider authenticationProvider, HttpMessageHandler innerHandler)
            {
                InnerHandler = innerHandler;
                _authenticationProvider = authenticationProvider;
            }

            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                await _authenticationProvider.AuthenticateRequestAsync(request);
                return await base.SendAsync(request, cancellationToken);
            }
        }

您遇到了一些问题:

  1. 您应该为 https://graph.microsoft.com 请求令牌,而不是 https://graph.windows.netgraph.windows.net 是较旧的 AAD Graph,而不是较新的 Microsoft Graph:

    AuthenticationResult authenticationResult = authContext
      .AcquireTokenAsync("https://graph.windows.net", clientCred).Result;
    
  2. 您不能将 /me 与客户端凭据授权一起使用。 Graph 将 /me 翻译成 /users/{currently authenticated user id}。由于您没有对用户进行身份验证,因此 "currently authenticated user id" 是 null:

    var uri = "https://graph.microsoft.com/v1.0/users/user@domain.onmicrosoft.com";
    
  3. 您正在设置授权 header 的值,但不是 scheme。您需要同时设置:

    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", token);
    
  4. 从您的问题中不清楚您请求了哪些范围或您是否已获得管理员同意。您需要确保您已请求应用程序范围 User.Read.All 从租户管理员那里收到 Admin Consent