如何使用OAuth进行身份验证以访问EWS API

时间:2017-07-31 12:10:25

标签: c# oauth exchangewebservices

我的网络服务目前正在进行基本的用户名/密码验证,以便订阅交换用户接收事件(如新邮件事件等),如下所示:

var service = new ExchangeService(exchangeVersion)
                                  {
                                      KeepAlive = true,
                                      Url = new Uri("some autodiscovery url"),
                                      Credentials = new NetworkCredential(username, password)
                                  };

var subscription = service.SubscribeToPushNotifications(
                                    new[] { inboxFolderFoldeID },
                                    new Uri("some post back url"),
                                    15,
                                    null,
                                    EventType.NewMail,
                                    EventType.Created,
                                    EventType.Deleted,
                                    EventType.Modified,
                                    EventType.Moved,
                                    EventType.Copied);

现在,我应该更换身份验证机制以使用OAuth协议。我看到了一些示例,但所有这些示例似乎都在讨论对客户端进行身份验证(https://msdn.microsoft.com/en-us/library/office/dn903761%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396),但我无法找到如何使用OAuth协议对Exchange用户进行身份验证的示例。任何代码示例都会有很大帮助。感谢。

3 个答案:

答案 0 :(得分:4)

目前尚不清楚您对“网络服务”的意思以及您目前如何获取用户名和密码。如果这是用户需要登录或传递凭据的某种类型的网站,那么您必须从浏览器启动OAuth2授权,如将客户端浏览器重定向到授权端点以启动implicit grant或{{ 3}}。一旦用户登录代码或访问令牌(取决于授权),用户将在OAuth2服务器(而不是您的应用程序)中显示登录屏幕,将返回到您可以在{{{{{ 1}}构造函数。

如果'web'服务是在用户计算机上运行的某项服务,则可以使用下述方法之一。

使用AuthenticationContext获取AccessToken

该示例似乎基于较早版本的code grant类。

AuthenticationContext似乎更新,ExchangeService现已重命名为AcquireToken / AcquireTokenAsync

无论您使用哪个版本,都无法像当前代码那样传递用户名和密码。但是,您可以让AcquireTokenSilentAsync方法提示输入凭据给用户。说实话,让您的应用程序直接处理这些用户机密更安全。在您知道之前,您将在数据库中存储纯文本密码(希望您还没有)。

在这两个版本中,这些方法都有很多重载,所有重载都有不同的参数和略有不同的功能。对于您的用例,我认为这些很有趣:

两个版本中的提示行为自动表示:当用户尚未缓存时,将要求用户提供凭据。两个PromptBehavior.Auto构造函数都允许您传递令牌缓存,这是您可以自己实现的。在内存,文件或数据库中缓存令牌(有关示例文件缓存实现,请参阅AcquireToken(string, string, Uri, PromptBehavior)。

手动获取AccessToken

如果您真的想在不提示用户的情况下从代码传递用户凭据,那么总会有办法解决。在这种情况下,您必须按照OAuth2规范/ RFC6749中的规定实施this article

巧合与否,我有一个名为Resource Owner Password Credentials grant的开源库实现了这个与AuthenticationContext一起使用,但无论如何,如果你想走这条路,你可以深入研究这些代码,尤其是从oauth2-client-handler开始。

使用访问令牌

获得访问令牌后,您可以继续this method上的示例,例如:

HttpClient

答案 1 :(得分:1)

如果有人仍在努力让它发挥作用。我们需要在应用程序的azure门户上载证书清单,然后使用相同的证书对客户端进行身份验证以获取访问令牌。有关详细信息,请参阅:https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/

答案 2 :(得分:0)

使用此 Microsoft Document 中的示例代码作为起点和这些库:

  • 微软身份客户端 4.27
  • EWS 托管 API v2.2

我能够在 Office 365 上成功验证并连接 Exchange。

      public void Connect_OAuth()
      {
         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" };

         AuthenticationResult authResult = null;

         try
         {
            authResult = cca.AcquireTokenForClient( ewsScopes ).ExecuteAsync().Result;
         }
         catch( Exception ex )
         {
            Console.WriteLine( "Error: " + ex );
         }

         try
         {
            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, "ccc@pppsystems.co.uk" );

            ewsClient.HttpHeaders.Add( "X-AnchorMailbox", "ccc@pppsystems.co.uk" );


            var folders = ewsClient.FindFolders( WellKnownFolderName.MsgFolderRoot, new FolderView( 10 ) );

            foreach( var folder in folders )
            {
               Console.WriteLine( "" + folder.DisplayName );
            }
         }
         catch( Exception ex )
         {
            Console.WriteLine( "Error: " + ex );
         }
      }

Microsoft 示例代码不起作用 - 对 AcquireTokenForClient 的异步调用从未返回。

通过在单独的 AcquireTokenForClient try 块中调用 catch 来捕获一般 Exception,删除 await 并使用 .Result,现在有效 - 没有其他任何改变。

我意识到这不是最佳实践,但是,无论有没有调试器,原始代码中的异步调用都没有返回。

在 Azure 设置中:

  • 使用了客户端机密文本字符串 - 不需要 x509 证书
  • 配置是“仅限应用的身份验证”

希望这有助于人们避免数小时的沮丧。