【问题标题】:How to get OAuth2 access token for EWS managed API in service/daemon application如何在服务/守护程序应用程序中获取 EWS 托管 API 的 OAuth2 访问令牌
【发布时间】:2025-11-27 03:25:05
【问题描述】:

场景

我在 Azure VM 上有一个 Exchange Online 环境和服务/守护程序(无交互式用户)应用程序。服务使用 EWS 托管 API 处理 any 租户用户邮箱中的电子邮件。现在 EWS 客户端使用基本身份验证,根据 Microsoft 的说法,EWS 将不支持访问 Exchange Online。

问题/问题

因此,我需要找到一种方法来获取服务/守护程序应用程序的有效访问令牌,以便与 EWS 托管 API 一起使用。

我的发现

以下article 显示了将 OAuth 2.0 与 EWS 托管 API 结合使用的示例。此示例有效,但它使用了获取同意的交互式方法(出现登录表单,允​​许用户验证自己并向应用程序授予请求的权限),不适合服务/守护程序应用场景,因为没有交互式用户。

对于服务/守护程序应用程序,我需要使用client credential 身份验证流程。

注册申请

https://aad.portal.azure.com 门户上使用管理员帐户我向 Azure Active Directory 注册了应用程序。为已注册的应用程序添加了客户端密码。

上述article 使用https://outlook.office.com/EWS.AccessAsUser.All 作为scope。但我没有在门户网站上找到此类 URL 的许可。在Office 365 Exchange Online>Application permissions>Mail下发现只有以下权限:

  1. https://outlook.office365.com/Mail.Read 允许应用在没有登录用户的情况下阅读所有邮箱中的邮件
  2. https://outlook.office365.com/Mail.ReadWrite 允许应用在没有登录用户的情况下创建、阅读、更新和删除所有邮箱中的邮件。

我添加了它们并为所有用户授予了管理员同意。

获取访问令牌

出于测试目的和简单性,我没有使用任何身份验证库(ADAL、MSAL 等)。我使用 Postman 获取访问令牌,然后在调试中设置 token 变量(参见帖子后面的代码 sn-p)。

我尝试了不同的端点来获取访问令牌。

  1. OAuth 2.0 令牌端点 (v2)
    POST: https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token
        grant_type=client_credentials
        client_id=*** 
        client_secret=***
        scope=https://outlook.office.com/EWS.AccessAsUser.All

发送此请求会产生以下错误响应:

AADSTS70011:提供的请求必须包含“范围”输入参数。为输入参数“范围”提供的值无效。范围https://outlook.office.com/EWS.AccessAsUser.All 无效。

我尝试将scope 更改为https://outlook.office.com/.default。访问令牌已返回,但它似乎对 EWS 无效。 EWS 客户端抛出 401 错误,x-ms-diagnostics 响应标头值如下:

2000008;reason="令牌不包含权限,或权限无法理解。";error_category="invalid_grant"

  1. OAuth 2.0 令牌端点 (v1)
    POST: https://login.microsoftonline.com/<TENANT_ID>/oauth2/token
        grant_type=client_credentials
        client_id=*** 
        client_secret=***
        resource=https://outlook.office.com

访问令牌已返回,但似乎对 EWS 无效。 EWS 客户端抛出 401 错误,x-ms-diagnostics 响应标头的值与 #1 中所述的相同。

将获取的访问令牌与 EWS 托管 API 一起使用

这是我用来测试 EWS 客户端的代码示例,其中使用了在 Postman 中获取的访问令牌:

var token = "...";
var client = new ExchangeService
{
    Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx"),
    Credentials = new OAuthCredentials(token),
    ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress,
                 "user@domain.onmicrosoft.com"),

};
var folder = Folder.Bind(client, WellKnownFolderName.SentItems);

【问题讨论】:

  • 我会假设使用客户端凭据或基于证书的身份验证,这些是特定于租户的。如果它是唯一一个可以的租户,但如果您需要支持任何人都可以用来访问他们自己的租户的产品,那么您需要他们执行相同的证书/客户端流程,或使用登录流程。后者您可以在服务应用程序中存储刷新令牌以获取访问令牌。前者,我知道有一种方法可以提供一个“应用程序”,365 管理员可以在 365 中安装并连接它,但还没有研究过。
  • 有人可以确认是否可以限制应用程序权限视图 EWS API 可访问的邮箱数量?
  • @Oleksii 您是否尝试过使用带有客户端 ID 和租户 ID 而不是令牌的 EWS API 进行连接

标签: c# oauth-2.0 office365 exchangewebservices


【解决方案1】:

我成功使用了这个方法:

  1. 安装 Microsoft 身份验证库模块 (MSAL.PS) https://www.powershellgallery.com/packages/MSAL.PS/4.2.1.3

  2. 按照 MSFT 说明配置代理访问:https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth

  3. 照常为服务帐户配置 ApplicationImpersonation

  4. 抓住你的令牌

    $cred = 获取凭据

    $clientid = ""

    $tenantid = ""

    $tok = Get-MsalToken -ClientId $clientid -TenantId $tenantid -UserCredential $cred -Scopes "https://outlook.office.com/EWS.AccessAsUser.All"

【讨论】:

    【解决方案2】:

    迟到的答案,但既然这似乎出现了,而我只是在处理这个......为什么不呢。

    如果您将 Microsoft 的 v2.0 URL 用于 OAUTH2(https://login.microsoftonline.com/common/oauth2/v2.0/authorize.../common/oauth2/v2.0/token),那么 Office 365 EWS 的范围是:

    https://outlook.office365.com/EWS.AccessAsUser.All

    您可能希望将此范围与“openid”(以获取登录用户的身份)和“offline_access”(以获取刷新令牌)结合使用。但是在使用客户端凭据时可能不需要offline_access(因为您不必每次需要访问令牌时都提示人类用户)。

    换句话说:

    params.add("client_id", "...")
    ...
    params.add("scope", "openid offline_access https://outlook.office365.com/EWS.AccessAsUser.All")
    

    如果使用 v1 OAUTH2 URL(https://login.microsoftonline.com/common/oauth2/authorize.../common/oauth2/token),那么您可以使用“资源”而不是“范围”。 Office 365 的资源是https://outlook.office365.com/

    或者换句话说:

    params.add("resource", "https://outlook.office365.com/")
    

    请注意,在后一种情况下,您不要求任何范围(不可能将“资源”与范围结合起来)。但是token会自动覆盖offline_access和openid范围。

    【讨论】:

      【解决方案3】:

      我们遇到了类似的问题:我们想使用服务帐户连接到单个邮箱,并且只使用 EWS API 做一些事情(例如在 GAL 中搜索),而 full_access_as_app 似乎有点过头了。 幸运的是,这是可能的:

      1. 关注普通"delegate" steps

      2. 并使用它通过用户名/密码获取令牌:

      ...
      var cred = new NetworkCredential("UserName", "Password");
      var authResult = await pca.AcquireTokenByUsernamePassword(new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" }, cred.UserName, cred.SecurePassword).ExecuteAsync();
      ...
      
      1. 要完成这项工作,您需要在“身份验证”>“高级设置”下启用“将应用程序视为公共客户端”,因为这使用“资源所有者密码凭据流”。 (这个SO answer对我帮助很大!)

      通过该设置,我们可以使用“传统”用户名/密码方式,但使用 OAuth 和 EWS API。

      【讨论】:

        【解决方案4】:

        我在为 EWS 实施 OAuth 时确实遇到了这个问题。我的应用程序使用EWS Managed API。这是我为使其正常工作所做的一切。

        1. 向应用程序添加了权限Office 365 Exchange Online &gt; full_access_as_app
        2. 已获取范围 https://outlook.office365.com/.default 的访问令牌。
        POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
        
        form-data = {
          client_id,
          client_secret,
          grant_type: 'client_credentials',
          scope: 'https://outlook.office365.com/.default',
        };
        
        1. 将访问令牌作为Authorization 标头和ExchangeImpersonation SOAP 标头添加到请求中。
        <SOAP-ENV:Header>
          <t:ExchangeImpersonation>
            <t:ConnectingSID>
              <t:PrimarySmtpAddress>user@domain.com</t:PrimarySmtpAddress>
            </t:ConnectingSID>
          </t:ExchangeImpersonation>
        </SOAP-ENV:Header>
        

        【讨论】:

          【解决方案5】:

          我在关注OAuth 2.0 client credentials flow 的 Microsoft 官方文档时遇到了同样的问题

          根据Microsoft identity platform and the OAuth 2.0 client credentials flow,范围“应该是你想要的资源的资源标识符(应用ID URI),后缀.default”(见default scope doc)。

          所以问题是如何将https://outlook.office.com/EWS.AccessAsUser.All转换为资源标识符。

          在实验中,我设法使用scope=https://outlook.office365.com/.default 使其工作。我为此授予了full_access_as_app(Office 365 Exchange Online / 应用程序权限)和got administrator consent

          【讨论】:

          【解决方案6】:

          您可以使用证书或机密来保护您的客户端应用程序。我需要两个权限才能让它工作是Calendars.ReadWrite.Allfull_access_as_app。我从未尝试通过 PostMan 获取我的令牌,但在 Microsoft.IdentityModel.Clients.ActiveDirectory 中使用 AcquireTokenAsync。在那个调用中,我使用的resource 参数是https://outlook.office365.com/。一旦你知道所有的小曲折,这很简单。并充分披露:在 MSFT 的支持帮助我度过难关之前,我是一只迷路的小狗。网络上的文档通常是过时的、相互矛盾的,或者充其量是令人困惑的。

          【讨论】:

          • 最终,我通过获得full_access_as_app 权限使 EWS 客户端按预期工作。它位于API permissions > Add a permission > APIs my organization uses > Office 365 Exchange Online > Application permissions > full_access_as_app。我使用 Postman 进行测试。
          • 这将允许在任何邮箱中进行任何修改。这不会通过大多数公司的安全审核,因此不是可接受的解决方案。
          【解决方案7】:

          【讨论】:

          最近更新 更多