使用 Microsoft Graph 无需任何用户交互即可获取电子邮件

Using Microsoft Graph to fetch emails without any user interaction

我想做的就是获取用户 ID 的电子邮件,其他用户无需登录其 Microsoft 帐户即可访问该用户 ID。我看过许多 SO 帖子 (this), code samples (this, this) and looked into the specs of OpenID and other docs (this),但仍然无法弄明白。

我已经在 Azure 门户中注册了应用程序并授予了所需的权限。使用 sample app 我可以获取用户列表,但不能获取电子邮件列表。我比较了用户查询和电子邮件查询的请求 headers。两者看起来一样。有人可以告诉我我做错了什么吗?

代码如下:

Startup.Auth.cs

public static string clientId = "<CLIENT ID>";
public static string clientSecret = <CLIENT SECRET>;
private static string authority = "https://login.microsoftonline.com/common/v2.0";
public static string redirectUri = "https://localhost:44316/";

private void ConfigureAuth(IAppBuilder app)
{
   app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

   app.UseCookieAuthentication(new CookieAuthenticationOptions());

   app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
   {
       ClientId = clientId,
       Authority = authority,
       RedirectUri = redirectUri,
       PostLogoutRedirectUri = redirectUri,
       //Scope = "openid profile offline_access Mail.Read",
       Scope = "email profile openid offline_access User.Read Mail.Read",
       //ResponseType = "id_token",
       ResponseType = "code id_token",
       TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, NameClaimType = "name" },
       Notifications = new OpenIdConnectAuthenticationNotifications
       {
          AuthenticationFailed = this.OnAuthenticationFailedAsync,
          SecurityTokenValidated = this.OnSecurityTokenValidatedAsync
        }
    });
}

SyncController.cs

private const string AuthorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
private const string MSGraphScope = "https://graph.microsoft.com/.default";
//private const string MSGraphQuery = "https://graph.microsoft.com/v1.0/users";
//private const string MSGraphQuery = "https://graph.microsoft.com/v1.0/me";
private const string MSGraphQuery = "https://graph.microsoft.com/v1.0/me/messages";

public async Task GetAsync(string tenantId)
{
    MSALCache appTokenCache = new MSALCache(Startup.clientId);

    // Get a token for the Microsoft Graph. If this line throws an exception for
    // any reason, we'll just let the exception be returned as a 500 response
    // to the caller, and show a generic error message to the user.
    ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(Startup.clientId, string.Format(AuthorityFormat, tenantId), Startup.redirectUri,
                                                                                   new ClientCredential(Startup.clientSecret), null, appTokenCache.GetMsalCacheInstance());
    AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new[] { MSGraphScope });

    HttpClient client = new HttpClient();

    // Uses SendAsync
    /*HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, MSGraphQuery);
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
    //request.Headers.Add("client-request-id", System.Guid.NewGuid().ToString());
    //request.Headers.Add("return-client-request-id", "true");
    HttpResponseMessage response = await client.SendAsync(request);*/

    // Uses GetAsync
    client.BaseAddress = new System.Uri(MSGraphQuery);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Add("Authorization", "Bearer " + authResult.AccessToken);
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    HttpResponseMessage response = await client.GetAsync(MSGraphQuery);
}

编辑 1: 以下是用户列表(有效)和电子邮件列表(无效)的请求和响应值:

Request body to fetch user list (working):

{Method: GET, RequestUri: 'https://graph.microsoft.com/v1.0/users', Version: 1.1, Content: <null>, Headers:
{
  Accept: application/json
  Authorization: Bearer eyJ0eXAiOiJKV...
}}


Response when fetching user list (working):

{StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Transfer-Encoding: chunked
  request-id: 02034f96-f519-4f5f-b47d-efb98dff0072
  client-request-id: 02034f96-f519-4f5f-b47d-efb98dff0072
  x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"South India","Slice":"SliceC","Ring":"5","ScaleUnit":"002","Host":"AGSFE_IN_8","ADSiteName":"INS"}}
  OData-Version: 4.0
  Duration: 76.0712
  Strict-Transport-Security: max-age=31536000
  Cache-Control: private
  Date: Fri, 02 Nov 2018 12:36:51 GMT
  Content-Type: application/json; odata.metadata=minimal; odata.streaming=true; IEEE754Compatible=false; charset=utf-8
}}


Request body to fetch emails (not working):

{Method: GET, RequestUri: 'https://graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages', Version: 1.1, Content: <null>, Headers:
{
  Accept: application/json
  Authorization: Bearer eyJ0eXAiOiJKV...
}}    

Response when fetching list of emails (not working):

{StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Transfer-Encoding: chunked
  request-id: 5b728866-b132-404f-9986-70fc56e57c3c
  client-request-id: 5b728866-b132-404f-9986-70fc56e57c3c
  x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"South India","Slice":"SliceC","Ring":"5","ScaleUnit":"002","Host":"AGSFE_IN_6","ADSiteName":"INS"}}
  Duration: 4205.8126
  Strict-Transport-Security: max-age=31536000
  Cache-Control: private
  Date: Fri, 02 Nov 2018 12:43:03 GMT
  Content-Type: application/json
}}

您正在使用 Client_Credentials 通过 REST 调用中的 /me 路径对应用 进行身份验证。这两个不能一起工作。

在幕后 /me 被翻译成 当前经过身份验证的用户 (即 /users/user@domain。因为您 没有 用户已通过身份验证,Graph 根本不可能将您的请求转换为可操作的调用。

您需要使用 id or their userPrincipalName:

显式引用用户
 /v1.0/users/123e4567-e89b-12d3-a456-426655440000/mailFolders('Inbox')/messages
 /v1.0/users/user@yourdomain.com/mailFolders('Inbox')/messages