【问题标题】:Hosting a WCF service using net.tcp binding in IIS 7 (unreachable from outside)在 IIS 7 中使用 net.tcp 绑定托管 WCF 服务(无法从外部访问)
【发布时间】:2015-05-20 09:01:22
【问题描述】:

我开发了一个 WCF 双工服务和一个 Windows Winforms 客户端通过 net.tcp 双工绑定进行通信。

两者都可以在我的 LAN 上进行通信和正常工作,WCF 服务托管在 Windows 8 工作站上的 IIS 7 上。

然后,我尝试在运行 Windows server 2008 R2 的租用专用服务器上托管 Web 上的 WCF 服务,该服务器具有固定 IP 地址:(94.23.220.199),运行 IIS 7 和 .Net 4.5.2。

WCF 服务已安装在 /ScgBroadcastorService 虚拟路径上,并且 net.tcp 协议已激活。 (实际上,所有 IIS 配置内容都与我 LAN 上的个人 IIS 完全一样)。因此,应该可以通过以下 URL 从外部访问该服务:“http://94.23.220.199/ScgBroadcastorService/Service.svc”。

如果您从浏览器访问此链接,您将获得一个正确的“ScgBroadcastorService 服务”页面,其中包含两个 wsdl 链接。 (这些链接正确引用了“94.23.220.199”IP 地址。

如果单击此链接,则可以正确获取 wsdl xml 文档。

所以由于可以从外部访问 wsdl 文档,我希望客户端能够与 WCF 服务进行通信..

但是,如果我启动客户端,我会收到以下异常:(对不起,我的家用计算机已本地化为法语......根异常是“服务器拒绝了客户端凭据。”)

这是完整的跟踪:

System.ServiceModel.Security.SecurityNegotiationException: Le serveur a rejeté les informations d'identification du client. ---> System.Security.Authentication.InvalidCredentialException: Le serveur a rejeté les informations d'identification du client. ---> System.ComponentModel.Win32Exception: La tentative d’ouverture de session a échoué
   --- Fin de la trace de la pile d'exception interne ---
   à System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.StartSendBlob(Byte[] message, LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.CheckCompletionBeforeNextSend(Byte[] message, LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.StartSendBlob(Byte[] message, LazyAsyncResult lazyResult)
   à System.Net.Security.NegoState.ProcessAuthentication(LazyAsyncResult lazyResult)
   à System.Net.Security.NegotiateStream.AuthenticateAsClient(NetworkCredential credential, ChannelBinding binding, String targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel)
   à System.Net.Security.NegotiateStream.AuthenticateAsClient(NetworkCredential credential, String targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel)
   à System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade(Stream stream, SecurityMessageProperty& remoteSecurity)
   --- Fin de la trace de la pile d'exception interne ---

Server stack trace: 
   à System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade(Stream stream, SecurityMessageProperty& remoteSecurity)
   à System.ServiceModel.Channels.StreamSecurityUpgradeInitiatorBase.InitiateUpgrade(Stream stream)
   à System.ServiceModel.Channels.ConnectionUpgradeHelper.InitiateUpgrade(StreamUpgradeInitiator upgradeInitiator, IConnection& connection, ClientFramingDecoder decoder, IDefaultCommunicationTimeouts defaultTimeouts, TimeoutHelper& timeoutHelper)
   à System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.SendPreamble(IConnection connection, ArraySegment`1 preamble, TimeoutHelper& timeoutHelper)
   à System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.DuplexConnectionPoolHelper.AcceptPooledConnection(IConnection connection, TimeoutHelper& timeoutHelper)
   à System.ServiceModel.Channels.ConnectionPoolHelper.EstablishConnection(TimeSpan timeout)
   à System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.OnOpen(TimeSpan timeout)
   à System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   à System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
   à System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   à System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
   à System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
   à System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   à System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   à System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

请注意,如果我直接从主机启动客户端,使用相同的客户端配置文件,客户端可以完美连接和通信!

这是当前安装在托管服务的服务器上的 web.config 文件:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="ScgServiceLibrary.ScgBroadcastorService">
        <endpoint binding="netTcpBinding" contract="ScgServiceLibrary.IScgBroadcastorService">
          <identity>
            <servicePrincipalName value="host/94.23.220.199" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
   <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

这是我从外部和主机使用的客户端配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
              <binding name="NetTcpBinding_IScgBroadcastorService">
                <security mode="None"></security>
              </binding>
            </netTcpBinding>
        </bindings>
        <client>
            <endpoint address="net.tcp://94.23.220.199/ScgBroadcastorService/Service.svc"
                binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IScgBroadcastorService"
                contract="ScgServiceLibrary.IScgBroadcastorService" name="NetTcpBinding_IScgBroadcastorService">
                <identity>
                    <servicePrincipalName value="host/94.23.220.199" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

注意我已经添加了

<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />

服务器上 web.config 文件末尾的行,以获取一个服务页面,其中包含两个 wsdl 链接上的 IP 地址。如果没有这一行,两个链接包括计算机名“ns304385”而不是 IP 地址,当然 wsdl 无法从外部获取。

感谢您帮助 mo 解决剩余的部署问题。我现在陷入困境,不知道该怎么做才能让我的客户访问我托管在网络上的 WCF 服务...

【问题讨论】:

  • 是否在 IIS 中的网站上激活了 net.tcp 绑定 (808:*)?很确定 Net.Tcp 侦听器适配器 WPA 的 Windows 服务也需要运行
  • 我已经在 IIS 中托管的 ScgBroadcastorService 应用程序的高级设置中激活了 net.tcp 协议。你是这个意思吗?
  • 是的,“net.tcp 侦听器适配器”服务正在运行。
  • 不完全。虽然您在 Advanced Settings.Enabled Protocols 中有“net.tcp”,但最终 net.tcp 必须出现在 web site node 的 Bindings 中,例如选择“默认网站”;然后单击绑定...。也检查一下,谢谢
  • 是的,我刚刚检查了这个。 net.tcp 协议出现在我的默认网站的绑定设置中(808.*)...

标签: c# .net wcf iis tcp


【解决方案1】:

好吧,我终于在半夜解决了这个问题……

我不得不关闭两边的 netTcpBinding 的安全性。

但是要找出如何在服务器端为需要双工通信的合同关闭它并不是那么简单。

这是 web.config 文件:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <bindings>
      <netTcpBinding>
        <binding name="customTcpBinding" maxReceivedMessageSize="5242880" maxConnections="10">
          <readerQuotas maxDepth="64" maxStringContentLength="5242880" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
          <security mode="None"></security>
        </binding>
      </netTcpBinding>
    </bindings>
    <services>
      <service name="ScgServiceLibrary.ScgBroadcastorService">
        <endpoint binding="netTcpBinding" bindingConfiguration="customTcpBinding" contract="ScgServiceLibrary.IScgBroadcastorService">
          <identity>
            <servicePrincipalName value="host/94.23.220.199" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
   <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

诀窍是添加一个将安全模式设置为“无”的 customTcpBinding,并使用 bindingConfiguration 属性在端点中引用这个新绑定。

不确定 customTcpBinding 的所有参数是否都是最优的,但它们对于双工合同来说是可以的。 (我的第一次尝试因双面合同而被拒绝)

在客户端,我还必须将绑定的安全模式设置为“无”。这是我在客户端的新配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
                <binding name="NetTcpBinding_IScgBroadcastorService">
                    <security mode="None"></security>
                </binding>
            </netTcpBinding>
        </bindings>
        <client>
            <endpoint address="net.tcp://94.23.220.199/ScgBroadcastorService/Service.svc"
                binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IScgBroadcastorService"
                contract="ScgServiceLibrary.IScgBroadcastorService" name="NetTcpBinding_IScgBroadcastorService">
                <identity>
                    <servicePrincipalName value="host/94.23.220.199" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

最后,我的 WCF Windows WinForm 客户端可以正常工作并与我的 Duplex WCF 服务通信!!!

不得不说,解决这个问题真是一场噩梦……

希望对我的问题的完整描述能帮助其他开发人员尝试在网络上托管他们的双工 net.Tcp 绑定 WCF 服务,并在找到修改配置文件的正确方法之前厌倦了所有意外异常。

晚安,伙计们!只睡三个小时就回去工作了……:-(

【讨论】:

  • Security mode = "none" - 通过 Internet 发送凭据不是很危险吗?
  • 据我了解,在客户端和服务器端都将安全模式设置为“无”意味着根本不发送和检查任何凭据。
  • 我不这么认为。 stackoverflow.com/questions/2748115/… 和 msdn.microsoft.com/en-us/library/ms734784(v=vs.110).aspx。如果您将凭据放入有效负载中,它将以未加密的方式传输。
  • 在我的应用程序中(如果您有兴趣,请参阅说明并在94.23.220.199 自行测试)我不传输任何凭据。我希望客户端是公开的,并且不需要检查来验证客户端应用程序的用户。 WCF 服务不关心谁在与他交谈。这不是经典的客户端/服务器架构。在大多数专业的客户端/服务器系统中,显然需要验证客户端用户确实是他们假装的身份,并且他们被授予访问服务的权限。这根本不是我的用例。
  • 我只是按照这句话说“所以我尝试在服务器上创建一个帐户,使用相同的名称和密码比我尝试启动客户端时在家用计算机上使用的帐户...而且...它有效!!!” 这让我相信您使用的是计算机(而不是用户)身份验证。我错过了什么吗?
【解决方案2】:

将安全模式设置为“无”不是您应该解决此问题的方法,因为将安全设置为“无”也会删除消息的机密性(加密)和完整性(签名)。

<security mode="None"></security>

要删除身份验证,您可以: 1) 将安全模式设置为消息

<bindings>
    <netTcpBinding>
        <binding name="NetTcpBinding_IScgBroadcastorService">
            <security mode="Message">
                <message clientCredentialType="None" />
            </security>
        </binding>
    </netTcpBinding>
</bindings>

2) 或将安全模式设置为传输

<bindings>
    <netTcpBinding>
        <binding name="NetTcpBinding_IScgBroadcastorService">
            <security mode="Transport">
                <transport clientCredentialType="None" />
            </security>
        </binding>
    </netTcpBinding>
</bindings>

我不得不承认我没有用你的代码尝试这个。 只是想确保阅读本文的人知道身份验证和消息安全不是一回事,并为他们指明正确的方向。

【讨论】:

    【解决方案3】:

    对于托管在远程计算机上的 Windows 服务中的双工 wcf 服务,我遇到了同样的错误。为了使它工作,我必须创建入站和出站规则以在 Windows 防火墙高级设置中打开服务端口。我还创建了一个服务器用户并将此代码包含在我的客户端中:

    this._client = new WcfService.WcfServiceClient(context);
    _client.ClientCredentials.Windows.ClientCredential.UserName = user;
    _client.ClientCredentials.Windows.ClientCredential.Password = password;
    

    【讨论】:

      【解决方案4】:

      哇!我刚刚找到了部分解决方案!

      至少解释一下为什么我无法从外部访问 WCF 服务!

      阅读:https://social.msdn.microsoft.com/Forums/vstudio/en-US/1551b4e1-8e15-4da2-b155-d398379809b3/the-server-has-rejected-the-client-credentials-in-wcf?forum=wcf

      所以我尝试在服务器上创建一个帐户,其名称和密码与我尝试启动客户端时在家用计算机上使用的帐户相同......并且......它有效!!!

      当然你会同意我的看法,这是不可接受的......

      我希望我的 WCF 服务能够接受来自网络上任何地方登录的用户的客户端连接,并使用他们自己的用户名/密码,我不想关心!

      所以,搜索以另一种方式继续......如何允许 WCF 客户端连接而无需在我的服务器上创建帐户?

      敬请期待……希望很快就能找到答案……

      【讨论】:

        猜你喜欢
        • 2011-07-21
        • 2015-01-28
        • 2011-10-24
        • 2014-04-29
        • 2016-03-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-01
        相关资源
        最近更新 更多