EWS 托管 API 双跳

Posted

技术标签:

【中文标题】EWS 托管 API 双跳【英文标题】:EWS Managed API Double Hop 【发布时间】:2017-01-02 10:18:53 【问题描述】:

我正在开发一个将在本地使用的 Intranet 站点。在企业中,用户可以像 OWA 一样使用这个站点,他们可以看到他们的收件箱、发送邮件等。为了实现这一点,我使用 EWS Managed Api 2.2 连接 Exchange Server (2010_sp1)。我正在使用 ASPNet MVC 5 进行开发。我正在我的安装了 IIS 10.0 的计算机。当我使用 IISExpress 进行开发时,使用默认凭据连接没有问题 >

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
service.UseDefaultCredentials = true;

我明白,这是因为 IIS Express 使用我的凭据作为默认凭据,因此连接服务没有错误。但是当我在我的计算机上使用 IIS 运行此代码时(在应用程序池标识中使用我的凭据,启用 ASPNet 模拟,启用 Windows 身份验证和 NEGOTIATE/NTML 提供程序)身份加载正确

HttpContext.User.Identity.Name > xxx\billgates
WindowsIdentity.GetCurrent().Name> xxx\billgates

但 Exchange 服务会在跟踪中返回这些 >

<Trace Tag="EwsRequestHttpHeaders" Tid="29" Time="2017-01-02 08:03:04Z">
POST /EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: ExchangeServicesClient/15.00.0913.015
Accept-Encoding: gzip,deflate 
</Trace>

<Trace Tag="EwsRequest" Tid="29" Time="2017-01-02 08:03:04Z" Version="15.00.0913.015">
  <?xml version="1.0" encoding="utf-8"?>
  <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Header>
      <t:RequestServerVersion Version="Exchange2010_SP1" />
    </soap:Header>
    <soap:Body>
      <m:GetFolder>
        <m:FolderShape>
          <t:BaseShape>AllProperties</t:BaseShape>
        </m:FolderShape>
        <m:FolderIds>
          <t:DistinguishedFolderId Id="inbox" />
        </m:FolderIds>
      </m:GetFolder>
    </soap:Body>
  </soap:Envelope>
</Trace>

<Trace Tag="EwsResponseHttpHeaders" Tid="29" Time="2017-01-02 08:03:04Z">
HTTP/1.1 401 Unauthorized
Server: Microsoft-IIS/7.5
WWW-Authenticate: Negotiate,NTLM
X-Powered-By: ASP.NET
Date: Mon, 02 Jan 2017 08:03:04 GMT
Content-Length: 0 
</Trace>

当我使用 EWEditor(https://ewseditor.codeplex.com/) 时,我看到使用默认凭据连接没有错误,并且在 powershell 中运行 klist 时出现类似 kerberos 的标记>

服务器:krbtgt/xxxxx.xxx.xx@xxxxx.xxx.xx KerbTicket 加密类型:RSADSI RC4-HMAC(NT) 结束时间:1/2/2017 21:35:37 续订时间:1/9/2017 11:35:37

服务器:krbtgt/xxxxx.xxx.xx@xxxxx.xxx.xx KerbTicket 加密类型:未知 (18) 结束时间:1/2/2017 21:35:37 续订时间:1/9/2017 11:35:37

服务器:HTTP/posta.xxxxx.xxx.xx@xxxxx.xxx.xx KerbTicket 加密类型:RSADSI RC4-HMAC(NT) 结束时间:1/2/2017 21:35:37 续订时间:1/9/2017 11:35:37

服务器:HTTP/autodiscover.xxxxx.xxx.xx@xxxxx.xxx.xx KerbTicket 加密类型:RSADSI RC4-HMAC(NT) 结束时间:1/2/2017 21:35:37 续订时间:1/9/2017 11:35:37

服务器:HTTP/xxxcas2.xxxxx.xxx.xx@xxxxx.xxx.xx KerbTicket 加密类型:未知 (18) 结束时间:1/2/2017 21:35:37 续订时间:1/9/2017 11:35:37

服务器:ldap/xxxDC2.xxxxx.xxx.xx@xxxxx.xxx.xx KerbTicket 加密类型:未知 (18) 结束时间:1/2/2017 21:35:37 续订时间:1/9/2017 11:35:37

有了这些票证,我假设 kerberos 可以工作,我的第一个问题是我是对的吗?我可以确定这些票证保证 keberos 可以与客户端访问服务器一起工作吗?

在搜索这个问题时,我看到这个问题很像这个博客所说的双跳问题>https://blogs.msdn.microsoft.com/dhruvkh/2012/04/15/the-double-hop-dogma/ .其实我的问题和这篇文章一模一样。像这篇文章一样,建议我使用我的帐户作为服务帐户并将 SPNS 添加到我的帐户中,并将其写入 IIS 中的应用程序池标识中

我使用的 SPN

http/autodiscover.xxxxxx.xxx.xx

http/posta.xxxxxx.xxx.xx

注意:我确定没有使用这些 SPN。

添加 spns 后,我还选中“信任此计算机以委托任何服务(仅限 Kerberos)”。在我的用户帐户的委派选项卡中。

我的另一个问题是喜欢在 3. step https://technet.microsoft.com/en-us/library/ff808312(v=exchg.141).aspx 上的帖子建议 我应该创建一个备用服务帐户而不是使用我的帐户吗?如果我必须使用 ASA,我应该在 IIS 应用程序池标识中添加备用服务帐户凭据吗?

感谢您的帮助。

【问题讨论】:

【参考方案1】:

从以前的帖子更新。 我已经让它工作了,我当前的设置不是我期望产生的结果。我在 Win 2012 R2 上使用 IIS 8.5

    AppPool:设置为集成管道 AppPool:加载用户配置文件:False AppPool:在为委派配置的域服务帐户下运行的 IIS 在应用程序中 > 身份验证。 ASP.Net 模拟:已禁用。 Windows 身份验证:启用 w/内核模式关闭。提供者:协商:Keberos。 配置编辑器 > system.webServer/security/authentication/windowsAuthentication useAppPoolCredentials: True。 useKernelMode: 本地安全策略 > 本地策略 > 用户权限分配:服务帐户必须位于“身份验证后模拟客户端”策略中列出的组中

修改 Aspnet.config (C:\Windows\Microsoft.NET\Framework64\v4.0.30319)

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <runtime>
        <legacyUnhandledExceptionPolicy enabled="false" />
        <legacyImpersonationPolicy enabled="false"/>
        <alwaysFlowImpersonationPolicy enabled="true"/>
        <SymbolReadingPolicy enabled="1" />
        <shadowCopyVerifyByTimestamp enabled="true"/>
    </runtime>
    <startup useLegacyV2RuntimeActivationPolicy="true" />
</configuration>

以下是正在运行的服务源。我留下了所有注释掉的代码,这样你就可以看到我一直在尝试的所有内容。请注意,只需要一些活动代码。

     //using(HostingEnvironment.Impersonate())
     //
        try
        
           //DC = new PrincipalContext(ContextType.Domain, myDomain);
           //GP = GroupPrincipal.FindByIdentity(DC, IdentityType.Name, Constant.QMS_GROUP_PRINCIPAL);
           //ImpersonationContext = ((System.Security.Principal.WindowsIdentity)System.Threading.Thread.CurrentPrincipal.Identity).Impersonate();

           //the service creation gets called regularly and 
           //autodiscovery is very slow
           if(PerformanceHelper.GetMailService() != null)
           
              return PerformanceHelper.GetMailService();
           

           //create a service
           ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);

           service.TraceListener = new TraceListener();
           service.TraceEnabled = true;
           service.TraceFlags = TraceFlags.All;
           service.CookieContainer = new CookieContainer(1);
           service.PreAuthenticate = true;

           //CredentialCache credCache = new CredentialCache();

           //credCache.Add(new Uri(Constant.QMS_EXCHANGE_SVC), "Negotiate",

           //       CredentialCache.DefaultNetworkCredentials);

           //HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri(Constant.QMS_EXCHANGE_SVC));

           //req.Credentials = credCache;

           //service.Credentials = credCache.GetCredential(new Uri(Constant.QMS_EXCHANGE_SVC), "Negotiate");

           NetworkCredential nc = new NetworkCredential();
           nc = CredentialCache.DefaultNetworkCredentials;
           //nc = (NetworkCredential)CredentialCache.DefaultCredentials;

           service.Credentials = nc;

           ErrorHelper.AddErrorMsg("makeExchangeConnection IsWindowsIdentityFlowSuppressed() (info): " 
              + Convert.ToString(System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed()));

           //WebCredentials wc = new WebCredentials(System.Net.CredentialCache.DefaultCredentials);

           //WebCredentials wc = new WebCredentials(System.Net.CredentialCache.DefaultCredentials);
           //service.Credentials = wc;

           ErrorHelper.AddErrorMsg("makeExchangeConnection (info): " + System.Threading.Thread.CurrentPrincipal.Identity.Name);

           //use the windows login credential
           service.UseDefaultCredentials = true;

           //the autodiscovery has been failing in dev. 
           //the catch should rescue the connetcion.
           //I'm disabling this for now and just
           //declaring the service via constant. The failing
           //auto discover is a big performance hit
           //especially since it can't work behind the 
           //firewall
           //try
           //
           //   service.AutodiscoverUrl(eMailAddress, redirectionUrlValidationCallback);
           //
           //catch
           //
           //   service.Url = new Uri(Constant.QMS_EXCHANGE_SVC);
           //

           service.Url = new Uri(Constant.QMS_EXCHANGE_SVC);

           /* 01/6/2017 Enable below to allow for impersonation on
            * on the exchange server. This is pending an exchange
            * adminstrator granting access for the service accounts.            
            */
           //if(ui.Email != null)
           //   service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, ui.Email);

           //cache the service
           PerformanceHelper.AddMailService(service);


           return service;
        

调用交换服务器 API 时,包装在模拟块中。

     public bool SendReply(string messageId, string response)
     UserInfoHelper ui = new UserInfoHelper();

     using(HostingEnvironment.Impersonate())
     

        try
        

           DC = new PrincipalContext(ContextType.Domain, Constant.QMS_DOMAIN);
           GP = GroupPrincipal.FindByIdentity(DC, IdentityType.Name, "Domain Users");
           ImpersonationContext = ((System.Security.Principal.WindowsIdentity)System.Threading.Thread.CurrentPrincipal.Identity).Impersonate();

           //do stuff
        
        catch(Exception e)
        
           ErrorHelper.AddErrorMsg("SendReply: " + e);
        
     

     return false;
  

最后,这很重要,客户端浏览器必须设置为客户端计算机上的委派。在 Chrome 中,您需要编辑注册表。在 FF 中,您需要在 about:config 中设置 uris 键。开箱即用的行为将因 kerberos 而失败。有关详细信息,请参阅此内容(https://answers.laserfiche.com/questions/50123/Does-single-sign-on-or-authentication-negotiation-not-work-on-Chrome-in-Weblink 和 https://dev.chromium.org/administrators/policy-list-3#AuthNegotiateDelegateWhitelist)。

以下是我的原帖。

很长时间以来,我一直在解决同样的问题。看看这个 (Unable to authenticate to ASP.NET Web Api service with HttpClient)。我已修改 Aspnet.config 文件无济于事。从我目前阅读的内容来看,System.Net.HttpWebRequest.GetResponse() 似乎在一个新线程上,并且模拟的凭据没有传输到新对象。我已将 IIS 身份验证提供程序设置为协商:kerberos 以确保委托应该/可以发生。

我应该创建一个备用服务帐户而不是使用我的 帐号?

根据您的配置,即 IIS 位于负载平衡器和防火墙后面,您可能需要一个自定义服务帐户,例如mydomain\my_service_account 需要 SPN http/my.webserver.com。然而,我有这个设置,它并不是让这个工作的秘诀。我开始认为唯一可行的方法是在交换帐户上设置模拟 (https://msdn.microsoft.com/en-us/library/office/dn722376(v=exchg.150).aspx)。在此计划下,您在设置服务时会调用如下所示的内容。如果 IIS 上的服务帐户无权在 Exchange 服务器上模拟,这也不起作用。此外,您需要传递服务帐户的身份,而不是模拟用户的身份。

if(UserInfo.Email != null)
      service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, UserInfo.Email);

我希望有人能提供比这更好的答案。以下是我目前在跟踪中看到的内容。

9: HTTP/1.1 401 未经授权 服务器:Microsoft-IIS/7.5 WWW-认证:协商,NTLM X-Powered-By: ASP.NET 日期:格林威治标准时间 2017 年 1 月 20 日星期五 20:59:29 内容长度:0

追踪>

10:GetUnreadMail ee:Microsoft.Exchange.WebServices.Data.ServiceRequestException:请求失败。远程服务器返回错误:(401) Unauthorized。 ---> System.Net.WebException:远程服务器返回错误:(401)未经授权。 在 System.Net.HttpWebRequest.GetResponse() 在 Microsoft.Exchange.WebServices.Data.EwsHttpWebRequest.Microsoft.Exchange.WebServices.Data.IEwsHttpWebRequest.GetResponse() 在 Microsoft.Exchange.WebServices.Data.ServiceRequestBase.GetEwsHttpWebResponse(IEwsHttpWebRequest 请求) --- 内部异常堆栈跟踪结束 --- 在 Microsoft.Exchange.WebServices.Data.ServiceRequestBase.GetEwsHttpWebResponse(IEwsHttpWebRequest 请求) 在 Microsoft.Exchange.WebServices.Data.ServiceRequestBase.ValidateAndEmitRequest(IEwsHttpWebRequest& 请求) 在 Microsoft.Exchange.WebServices.Data.MultiResponseServiceRequest`1.Execute() 在 Microsoft.Exchange.WebServices.Data.ExchangeService.FindItems(FolderId parentFolderId,SearchFilter searchFilter,ViewBase 视图) 在 Microsoft.Exchange.WebServices.Data.ExchangeService.FindItems(WellKnownFolderName parentFolderName,SearchFilter searchFilter,ViewBase 视图) 在 QDoc.Helpers.ExchangeHelper.GetUnreadMail(ExchangeParam ExchangeQueryParam, MailItemList ExchangeResults, String myDomain) 在 c:\Inet\QDoc\QDoc\Helpers\ExchangeHelper.cs:line 186

【讨论】:

以上是关于EWS 托管 API 双跳的主要内容,如果未能解决你的问题,请参考以下文章

Folder.WellKnownFolderName 在 EWS 托管 API 和 Exchange 2013 中为空

使用 EWS 托管 API 创建共享邮箱

如何使用 EWS 托管 API 从 Microsoft Exchange 检索所有联系人?

如何使用 EWS 托管 API 2.0 创建笔记

使用 EWS 托管 API 读取自定义列

o365 和 Exchange 2010 的 EWS 托管 API 的 OAuth 身份验证