凭证未在生产服务器中的WCF调用上传递

Chr*_*ell 5 asp.net wcf

场景

我们有一个Web应用程序,使用以下代码调用Web服务(在IIS中托管在同一台机器上): -

using (HttpContext.Current.Request.LogonUserIdentity.Impersonate())
{
    var client = new Services.ReportFormatter(endpointName);  // endpoint name is configured in config
                                                       // Services.ReportFormatter is the generated client code using svcutil
    var response = client.DoWork();
}
Run Code Online (Sandbox Code Playgroud)

代码的意图是使用Web应用程序用户的凭据执行WCF调用(我希望这显然是显而易见的!)并且代码就像Dev和QA机器上的魅力一样.不用说,它在生产中失败了!

问题

生成的.Net异常是

The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate,NTLM,Basic realm="Services"'. InnerException:The remote server returned an error: (401) Unauthorized.

有趣的是,IIS日志表明没有在WCF请求上传递用户凭据

Fields: 
cs-method cs-uri-stem cs-uri-query              s-port cs-username c-ip sc-status sc-substatus sc-win32-status 
GET       /MyWebApp/MyPage.aspx                   - 443 chrisf x.x.x.x
POST      /MyServicesApp/ReportFormatter.svc/ntlm - 80  -            x.x.x.x 401 2 2148074254
POST      /MyServicesApp/ReportFormatter.svc/ntlm - 80  -            x.x.x.x 401 1 0
POST      /MyServicesApp/ReportFormatter.svc/ntlm - 80  -            x.x.x.x 401 1 2148074252
Run Code Online (Sandbox Code Playgroud)

请注意,对Web应用程序的请求记录了我的用户名 - 但是对服务的请求没有.将这些日志与我的开发机器中的日志进行比较,我在两个请求中都看到了我的名字 - 所以我认为这是失败的原因.

这是WCF配置 - 尽管它在Dev + QA中工作的事实让我怀疑这不是错误的.任何想法/帮助将不胜感激.

Web应用程序(即WCF客户端)配置.

<bindings>
  <basicHttpBinding>

    <binding name="ReportFormatter" closeTimeout="00:01:00" openTimeout="00:01:00"
        receiveTimeout="00:10:00" sendTimeout="00:05:00" allowCookies="false"
        bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
        maxBufferSize="6553600" maxBufferPoolSize="524288" maxReceivedMessageSize="6553600"
        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
        useDefaultWebProxy="true" >
      <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
      <security mode="TransportCredentialOnly">
        <transport clientCredentialType="Windows" />
        <message clientCredentialType="UserName" />
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

<client>
  <endpoint binding="basicHttpBinding" bindingConfiguration="ReportFormatter" contract="Services.ReportFormatter"
    name="ReportFormatter_Endpoint" address="http://localhost/MyServicesApp/ReportFormatter.svc/ntlm"/>
</client>
Run Code Online (Sandbox Code Playgroud)

Web服务应用程序的配置文件包含此内容

    <bindings>
        <basicHttpBinding>
            <binding name="BasicHttpWindowsBindingWinCred" maxReceivedMessageSize="20000000">
                <readerQuotas maxStringContentLength="20000000" maxArrayLength="1000000" maxBytesPerRead="20000000" />
                <security mode="TransportCredentialOnly">
                    <!-- clientCredentialType Windows requires IIS to be configured to support Windows authentication-->
                    <transport clientCredentialType="Windows" />
                    <message clientCredentialType="UserName" />
                </security>
            </binding>
        </basicHttpBinding>
    </bindings>## Heading ##
    <services>
      <service behaviorConfiguration="IIS.Service1Behavior" name="Services.FormatReportImpl">
          <endpoint address="ntlm" binding="basicHttpBinding" bindingConfiguration="BasicHttpWindowsBindingWinCred" name="FormatReportBindingWindowsAuthentication" contract="Services.ReportFormatter" bindingNamespace="http://www.donkey.com/Reporting" />
      </service>
    </services>
Run Code Online (Sandbox Code Playgroud)

OS详细信息

制作:Win 2003 32bit SP2(IIS 6.0)

QA:Win 2003 32位SP2(IIS 6.0)

开发:各种各样!我自己的系统是Win7 64(IIS 7.5) - 我们在Vista上也有开发人员.


问题的原因和解决方案

像往常一样,魔鬼在细节.为什么哦为什么总是在细节!如果他们帮助其他有类似问题的人,则包括血腥细节.

原因

为了使问题尽可能简单,我没有提到Web应用程序和Web服务未托管在默认的IIS网站中.相反,它们托管在一个单独的网站上(原因是我们在同一台服务器上托管了几个应用程序.+同一个应用程序的版本),我们使用主机头信息来识别网站.

因此,只能通过"app.companyName.com"之类的URL访问生产站点(和Web服务).因此,当我说客户端web.config有时,我撒了谎address="http://localhost/MyServicesApp/ReportFormatter.svc/ntlm"

它实际上有

address="http://app.companyName.com/MyServicesApp/ReportFormatter.svc/ntlm"

我觉得现在有点愚蠢,因为我知道我们无法跨越机器边界传递NTLM身份验证令牌 - 就WCF客户端而言,"app.companyName.com"是一个远程机器; 它不知道DNS映射回到同一台机器 - 它可能没有想法(继续阅读).

借口

为了原谅我的愚蠢,这是一个借口:我在问题中撒谎的原因是我已经检查过上述类型的设置在QA服务器上有效.我检查了以下工作在客户端的WCF配置地址工作在QA机器上.

address="http://qaServerName.domainName.local/MyServicesApp/ReportFormatter.svc/ntlm"

我在逻辑上认为,该地址必须转到域DNS才能解析,因此逻辑上会经历与"app.companyName.com"相同的处理.我甚至设置了"dnsAliasForTheQAServer" 的DNS 别名(在我们的本地域上),然后测试了以下工作

address="http://dnsAliasForTheQAServer.domainName.local/MyServicesApp/ReportFormatter.svc/ntlm"

它确实做到了!现在你必须承认,如果机器认为它被称为"qaServerName",那么你会认为"dnsAliasForTheQAServer.domainName.local"将被视为远程地址.所以我推断这可能不是导致生产服务器失败的问题.

只有当绝望导致我们实际创建一个新的DNS记录("qaServerNameDnsRecord2"),我们设法重现QA机器上的错误!因此,我得出结论,在WCF客户端和/或DNS上进行了一些非常聪明的名称解析,解析了以下主机名

  • "qaServerName"
  • "qaServerName.domainName.local"
  • "dnsAliasForTheQAServer.domainName.local"

到qaServerName - 因此逻辑上是"Localhost"(因为客户端和服务器都在同一台机器上)但是当主机名是时,名称解析并不聪明

  • "qaServerNameDnsRecord2"
  • "qaServerNameDnsRecord2.domainName.local"

我相信如果我知道关于DNS的事情或者Windows如何解析名称,那么这不会是一个惊喜.但我了解到,将来我不会那么快

解决方案

知道问题是地址解析为一个看起来像是远程的地址,解决方案很简单 - 只需使地址看起来是本地的(如果Web服务托管在默认的超级网站中)简单 - 只需将其address="http://localhost/..."作为端点地址!).

我尝试的第一件事是编辑QA服务器上的hosts文件,并添加一个将'app.companyName.com'映射到127.0.0.1的条目,但这不起作用.

所以我做的下一个(相当不优雅)的事情是为主机标头值为网站添加额外的标识127.0.0.1,然后将配置更改为读取address="http://127.0.0.1/..."(我想我可以使用localhost而不是127.0.0.1).这就把一切都排好了.

use*_*238 2

我想在生产中 WCF 和 Web 服务器运行在不同的机器上,而在 DEV 和 QA 中则不是?

您需要通过 Kerberos 身份验证委派用户身份才能模拟 WCF。原因:WCF 不知道用户凭据,因此无法正确模拟。在 Kerberos 场景中,将使用身份验证令牌而不是真实凭据。

如果您需要一个起点来了解 Kerberos 主题,请在 google 中搜索“Kerberos IIS Delegation 2003 windows authentication”或类似内容。