EWS托管API双跳

时间:2017-01-02 10:18:54

标签: asp.net-mvc iis exchange-server-2010 ews-managed-api kerberos-delegation

我正在开发一个将在内部部署中使用的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 tickes,例如&gt;

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

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

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

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

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

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

使用这些门票我认为kerberos有效,我的第一个问题是我是对的吗?我能确定这些门票可以保证keberos能够与客户端访问服务器配合使用吗?

在搜索此问题时,我发现此问题非常像双跳问题,如此博客说&gt; 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.步骤建议https://technet.microsoft.com/en-us/library/ff808312(v=exchg.141).aspx 我应该创建一个替代服务帐户而不是使用我的帐户吗?如果我必须使用ASA,我应该在IIS应用程序池标识中添加备用服务帐户凭据吗?

感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

  

从上一篇文章更新。   我已经让这个工作,我目前的设置不是我期望产生的结果。我在Win 2012 R2上使用IIS 8.5

  1. AppPool:设置为集成管道
  2. AppPool:加载用户个人资料:错误
  3. AppPool:在为委派
  4. 配置的域服务帐户下运行的IIS
  5. 在App&gt;验证。 ASP.Net模拟:已禁用。 Windows身份验证:启用内核模式关闭。提供商:谈判:Keberos
  6. 配置编辑器&gt; system.webServer / security / authentication / windowsAuthentication useAppPoolCredentials: True 。 useKernelMode: False
  7. 本地安全政策&gt;地方政策&gt;用户权限分配:服务帐户必须位于策略中列出的组中;#34;在身份验证后模拟客户端&#34;
  8.   

    对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;
            }
    
      

    调用Exchange Server 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-Weblinkhttps://dev.chromium.org/administrators/policy-list-3#AuthNegotiateDelegateWhitelist)。

    Registry Modification for Chrome

      

    以下是我原来的帖子。

    我长期以来一直在对同一件事进行故障排除。看看这个(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:&lt; Trace Tag =&#34; EwsResponseHttpHeaders&#34; TID =&#34; 38&#34;时间=&#34; 2017-01-20 20:59:29Z&#34;&gt; HTTP / 1.1 401未经授权 服务器:Microsoft-IIS / 7.5 WWW-Authenticate:谈判,NTLM X-Powered-By:ASP.NET 日期:2017年1月20日星期五20:59:29 GMT 内容长度:0

    &LT; /跟踪和GT;

    10:GetUnreadMail ee:Microsoft.Exchange.WebServices.Data.ServiceRequestException:请求失败。远程服务器返回错误:(401)未经授权。 ---&GT; 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&amp; request)    在Microsoft.Exchange.WebServices.Data.MultiResponseServiceRequest`1.Execute()    在Microsoft.Exchange.WebServices.Data.ExchangeService.FindItems(FolderId parentFolderId,SearchFilter searchFilter,ViewBase view)    在Microsoft.Exchange.WebServices.Data.ExchangeService.FindItems(WellKnownFolderName parentFolderName,SearchFilter searchFilter,ViewBase view)    在QDoc.Helpers.ExchangeHelper.GetUnreadMail(ExchangeParam ExchangeQueryParam,MailItemList ExchangeResults,String myDomain)中的c:\ Inet \ QDoc \ QDoc \ Helpers \ ExchangeHelper.cs:第186行