使用 OAuth 对 EWS 应用程序进行身份验证

了解如何一起使用 OAuth 身份验证和 EWS 托管 API 应用程序。

可以使用 Microsoft Entra 提供的 OAuth 身份验证服务,使 EWS 托管 API 应用程序能够访问 Office 365 中的Exchange Online。 若要将 OAuth 用于你的应用程序,需要执行以下操作:

  1. 向 Microsoft Entra 注册应用程序
  2. 添加代码以获取身份验证令牌,以获取来自令牌服务器的身份验证令牌。
  3. 对你发送的 EWS 请求添加身份验证令牌

注意

对 EWS 的 OAuth 身份验证仅在作为 Microsoft 365 一部分的 Exchange Online 中可用。 使用 OAuth 的 EWS 应用程序必须注册到 Microsoft Entra。

若要使用本文中的代码,你需要以下访问权限:

可以用于访问 Exchange Online 中的 EWS API 的 OAuth 权限类型分为两种。 继续教程前,需要选择要使用的特定权限类型。

  • 委派权限供已有用户登录的应用使用。 对于这些应用,用户或管理员同意应用请求的权限,并且应用可在调用 API 时充当已登录的用户。
  • 应用程序权限由无需用户登录即可运行的应用使用;例如,作为后台服务或守护程序运行的应用,并且可以访问多个邮箱。

注册应用程序

若要使用 OAuth,应用程序必须具有由 Microsoft Entra 颁发的应用程序 ID。 在本教程中,假定应用程序是控制台应用程序,因此需要使用 Microsoft Entra 将应用程序注册为公共客户端。 可以在 Microsoft Entra 管理中心或使用 Microsoft Graph 注册应用程序。

  1. 打开浏览器,导航到Microsoft Entra 管理中心并使用工作或学校帐户登录。

  2. 在左侧导航栏中选择“标识”,然后选择“应用程序”下的“应用注册”。

  3. 选择“新注册”。 在“注册应用”页上,按如下方式设置值。

    • 名称 设置为应用的友好名称。
    • 受支持的帐户类型 设置为对你的方案有意义的选择。
    • 若要 重定向 URI,请将下拉列表更改为 公共客户端(移动 & 桌面) 并将值设置为 “https://login.microsoftonline.com/common/oauth2/nativeclient”。
  4. 选择“注册”。 在下一页,复制“应用(客户端)ID”和目录(租户)ID的值,然后保存,你将在下一步中用到它们。

注意

开发人员可以使用工作或学校帐户登录,以在 Entra ID 目录中注册应用,或使用 Entra ID 目录中的来宾个人帐户 (MSA) 登录。 如果开发人员没有 Entra ID 目录,他们可以从 M365 开发人员计划免费获取一个。

配置以进行委派身份验证

如果应用程序使用委派的身份验证,则无需进行进一步配置。 Microsoft 标识平台 允许应用动态请求权限,因此无需预配置应用注册的权限。 但是,在某些情况下(例如 流代表)预配置权限是必需的。 使用以下步骤预配置 EWS 权限。

  1. 在“管理”下的左侧导航中,选择 “清单”。

  2. 找到清单中的“requiredResourceAccess”属性,然后在方括号内添加以下内容([]):

    {
        "resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
        "resourceAccess": [
            {
                "id": "3b5f3d61-589b-4a3c-a359-5dd4b5ee5bd5",
                "type": "Scope"
            }
        ]
    }
    
  3. 选择“保存”。

  4. 在“管理”下选择 “API 权限”。 确认列出了 EWS.AccessAsUser.All 权限。

配置仅应用身份验证

若要使用“应用程序权限”,请执行以下额外步骤。

  1. 在“管理”下的左侧导航中,选择 “清单”。

  2. 找到清单中的“requiredResourceAccess”属性,然后在方括号内添加以下内容([]):

    {
        "resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
        "resourceAccess": [
            {
                "id": "dc890d15-9560-4a4c-9b7f-a736ec74ec40",
                "type": "Role"
            }
        ]
    }
    
  3. 选择“保存”。

  4. 在“管理”下选择 “API 权限”。 确认列出了 full_access_as_app 权限。

  5. 选择“对组织授予管理员许可”,并接受许可对话框。

  6. 在“管理”下的左侧导航中,选择“证书 & 机密”。

  7. 选择“新建客户端机密”,并输入简短说明,然后选择“添加”。

  8. 复制新添加客户端机密的“”并保存,你等下会用到它。

添加代码以获取身份验证令牌

以下的代码片段显示了如何使用 Microsoft 身份验证库获取代理权限和应用程序权限的身份验证令牌。 这些片段假定执行身份验证请求所需的信息都存储在了应用程序的 App.config 文件中。 这些示例不包括错误检查,请参阅“代码示例”,查看完整代码。

获取委派身份验证的令牌

// Using Microsoft.Identity.Client 4.22.0

// Configure the MSAL client to get tokens
var pcaOptions = new PublicClientApplicationOptions
{
    ClientId = ConfigurationManager.AppSettings["appId"],
    TenantId = ConfigurationManager.AppSettings["tenantId"]
};

var pca = PublicClientApplicationBuilder
    .CreateWithApplicationOptions(pcaOptions).Build();

// The permission scope required for EWS access
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" };

// Make the interactive token request
var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync();

获取仅限应用的身份验证令牌

// Using Microsoft.Identity.Client 4.22.0
var cca = ConfidentialClientApplicationBuilder
    .Create(ConfigurationManager.AppSettings["appId"])
    .WithClientSecret(ConfigurationManager.AppSettings["clientSecret"])
    .WithTenantId(ConfigurationManager.AppSettings["tenantId"])
    .Build();

// The permission scope required for EWS access
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };

//Make the token request
var authResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();

向 EWS 请求添加身份验证令牌

收到 AuthenticationResult 对象后,可以使用 AccessToken 属性来获取令牌服务发出的令牌。

// Configure the ExchangeService with the access token
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);

若要使用“应用程序权限”,还需要显式模拟想要访问的邮箱。

//Impersonate the mailbox you'd like to access.
ewsClient.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "test@demotenant.onmicrosoft.com");

代码示例

委派身份验证

下面是完整的代码示例,该示例演示了如何使用“委派身份验证”,构建被 OAuth 身份验证通过的 EWS 请求。

using Microsoft.Exchange.WebServices.Data;
using Microsoft.Identity.Client;
using System;
using System.Configuration;

namespace EwsOAuth
{
    class Program
    {
        static async System.Threading.Tasks.Task Main(string[] args)
        {
            // Using Microsoft.Identity.Client 4.22.0

            // Configure the MSAL client to get tokens
            var pcaOptions = new PublicClientApplicationOptions
            {
                ClientId = ConfigurationManager.AppSettings["appId"],
                TenantId = ConfigurationManager.AppSettings["tenantId"]
            };

            var pca = PublicClientApplicationBuilder
                .CreateWithApplicationOptions(pcaOptions).Build();

            // The permission scope required for EWS access
            var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" };

            try
            {
                // Make the interactive token request
                var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync();

                // Configure the ExchangeService with the access token
                var ewsClient = new ExchangeService();
                ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
                ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);

                // Make an EWS call
                var folders = ewsClient.FindFolders(WellKnownFolderName.MsgFolderRoot, new FolderView(10));
                foreach(var folder in folders)
                {
                    Console.WriteLine($"Folder: {folder.DisplayName}");
                }
            }
            catch (MsalException ex)
            {
                Console.WriteLine($"Error acquiring access token: {ex}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex}");
            }

            if (System.Diagnostics.Debugger.IsAttached)
            {
                Console.WriteLine("Hit any key to exit...");
                Console.ReadKey();
            }
        }
    }
}

仅限应用的身份验证

仅限应用的身份验证”,构建被 OAuth 身份验证通过的 EWS 请求。

注意

使用模拟时,必须始终使用 X-AnchorMailbox 请求页眉,该页眉应设置为模拟邮箱的 SMTP 地址。

using Microsoft.Exchange.WebServices.Data;
using Microsoft.Identity.Client;
using System;
using System.Configuration;

namespace EwsOAuth
{
    class Program
    {
        static async System.Threading.Tasks.Task Main(string[] args)
        {
            // Using Microsoft.Identity.Client 4.22.0
            var cca = ConfidentialClientApplicationBuilder
                .Create(ConfigurationManager.AppSettings["appId"])
                .WithClientSecret(ConfigurationManager.AppSettings["clientSecret"])
                .WithTenantId(ConfigurationManager.AppSettings["tenantId"])
                .Build();

            var ewsScopes = new string[] { "https://outlook.office365.com/.default" };

            try
            {
                var authResult = await cca.AcquireTokenForClient(ewsScopes)
                    .ExecuteAsync();

                // Configure the ExchangeService with the access token
                var ewsClient = new ExchangeService();
                ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
                ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
                ewsClient.ImpersonatedUserId =
                    new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "meganb@contoso.onmicrosoft.com");

                //Include x-anchormailbox header
                ewsClient.HttpHeaders.Add("X-AnchorMailbox", "meganb@contoso.onmicrosoft.com");

                // Make an EWS call
                var folders = ewsClient.FindFolders(WellKnownFolderName.MsgFolderRoot, new FolderView(10));
                foreach(var folder in folders)
                {
                    Console.WriteLine($"Folder: {folder.DisplayName}");
                }
            }
            catch (MsalException ex)
            {
                Console.WriteLine($"Error acquiring access token: {ex}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex}");
            }

            if (System.Diagnostics.Debugger.IsAttached)
            {
                Console.WriteLine("Hit any key to exit...");
                Console.ReadKey();
            }
        }
    }
}

两种情况下的示例代码都需要 App.config 文件,其中包含以下条目:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
  </startup>
  <appSettings>
    <!-- The application ID from your app registration -->
    <add key="appId" value="YOUR_APP_ID_HERE" />
    <!-- The tenant ID copied from your app registration -->
    <add key="tenantId" value="YOUR_TENANT_ID_HERE"/>
    <!-- The application's client secret from your app registration. Needed for application permission access -->
    <add key="clientSecret" value="YOUR_CLIENT_SECRET_HERE"/>
  </appSettings>
</configuration>

另请参阅