如何在服务/守护程序应用程序中为EWS托管API获取OAuth2访问令牌

时间:2019-07-12 15:19:30

标签: c# oauth-2.0 office365 exchangewebservices

场景

我在Azure VM上有一个Exchange Online环境和服务/ daemin(无交互式用户)应用程序。服务使用EWS托管API来处理任何租户用户邮箱中的电子邮件。现在,EWS客户端使用基本身份验证,根据Microsoft的说法,EWS将不再支持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变量(请参阅后面的代码片段)。

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

  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:提供的请求必须包含“作用域”输入参数。输入参数“ scope”提供的值无效。范围https://outlook.office.com/EWS.AccessAsUser.All无效。

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

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中的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); 相同。

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

这是我用来用Postman中获得的访问令牌测试EWS客户端的代码示例:

keepOrder = (a, b) => {
    return a;
}

7 个答案:

答案 0 :(得分:2)

您可以使用证书或密码来保护客户端应用程序。要使其正常工作,我需要两个许可权:Calendars.ReadWrite.Allfull_access_as_app。我从未尝试通过 PostMan 获取令牌,而是在AcquireTokenAsync中使用Microsoft.IdentityModel.Clients.ActiveDirectory。在该调用中,我使用的resource参数是https://outlook.office365.com/。一旦您知道所有小小的曲折,就非常简单。并全面披露:直到MSFT支持帮助我完成这项工作之前,我还是一只迷失的小狗。网络上的文档经常过时,冲突或充其量是令人困惑的。

答案 1 :(得分:2)

我们遇到了类似的问题:我们想使用服务帐户连接到单个邮箱,并且仅使用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。

答案 2 :(得分:1)

答案 3 :(得分:0)

在关注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

答案 4 :(得分:0)

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

  1. 向应用程序添加了权限Office 365 Exchange Online > 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 :(得分:0)

最新答案,但是由于这似乎要解决,而我只是在处理此问题……为什么不这样做。

如果您将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/")

请注意,在后一种情况下,您不需要任何作用域(不可能将“资源”与作用域结合使用)。但是令牌会自动覆盖offline_access和openid范围。

答案 6 :(得分:0)

我成功使用了此方法:

  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”