WCF client endpoint: SecurityNegotiationException

2020-05-22 05:03发布

I'm having a strange situation here. I got it working, but I don't understand why. Situation is as follows:

There is a WCF service which my application (a website) has to call. The WCF service exposes a netTcpBinding and requires Transport Security (Windows). Client and server are in the same domain, but on different servers.
So generating a client results in the following config (mostly defaults)

<system.serviceModel>
    <bindings>
      <netTcpBinding>
         <binding name="MyTcpEndpoint" ...>          
              <reliableSession ordered="true" inactivityTimeout="00:10:00"
                              enabled="false" />
             <security mode="Transport">
                <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign"/>
                <message clientCredentialType="Windows" />
            </security>
        </binding>
      </netTcpBinding>
    </bindings>
    <client> 
        <endpoint address="net.tcp://localhost:xxxxx/xxxx/xxx/1.0" 
                   binding="netTcpBinding" bindingConfiguration="MyTcpEndpoint" 
                   contract="Service.IMyService" name="TcpEndpoint"/>
    </client>
</system.serviceModel>

When I run the website and make the call to the service, I get the following error:

System.ServiceModel.Security.SecurityNegotiationException: Either the target name is incorrect or the server has rejected the client credentials. ---> System.Security.Authentication.InvalidCredentialException: Either the target name is incorrect or the server has rejected the client credentials. ---> System.ComponentModel.Win32Exception: The logon attempt failed
    --- End of inner exception stack trace ---
    at System.Net.Security.NegoState.EndProcessAuthentication(IAsyncResult result)
    at System.Net.Security.NegotiateStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
    at System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.InitiateUpgradeAsyncResult.OnCompleteAuthenticateAsClient(IAsyncResult result)
    at System.ServiceModel.Channels.StreamSecurityUpgradeInitiatorAsyncResult.CompleteAuthenticateAsClient(IAsyncResult result)
    --- End of inner exception stack trace ---

Server stack trace: 
    at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result)
    at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
    at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
....

Now, if I just alter the configuration of the client like so:

    <endpoint address="net.tcp://localhost:xxxxx/xxxx/xxx/1.0" 
               binding="netTcpBinding" bindingConfiguration="MyTcpEndpoint" 
               contract="Service.IMyService" name="TcpEndpoint">
        <identity>
            <dns />
        </identity> 
  </endpoint>

everything works and my server happily reports that it got called by the service account which hosts the AppPool for my website. All good.

My question now is: why does this work? What does this do? I got to this solution by mere trial-and-error. To me it seems that all the <dns /> tag does is tell the client to use the default DNS for authentication, but doesn't it do that anyway?

UPDATE
So after some more research and trial-and-error, I still haven't found an answer to this problem. In some cases, if I don't provide the <dns />, I get the Credentials rejected error, but if I provide the <dns value="whatever"/>config, it works. Why?

3条回答
三岁会撩人
2楼-- · 2020-05-22 05:11

Not an answer, but the same "trick" works if you create the EndpointAddress via code:

// does fail on some machines for some users 
// (I have no explanation here - just crazy)
var address = new EndpointAddress(new Uri(url));

// will work and the dns entry doesn't matter
address = new EndpointAddress(new Uri(url), UpnEndpointIdentity.CreateDnsIdentity(""));

It's strange and I don't know why this is working, but it seems to help.

查看更多
ゆ 、 Hurt°
3楼-- · 2020-05-22 05:23

<dns/> tag allows the client to verify the server identity. For example if you said <dns value="google.com"/> it would verify that the WCF server provides google.com identity. Since you say <dns/> it probably just allows everybody to serve you.

More info at Service Identity and Authentication

查看更多
家丑人穷心不美
4楼-- · 2020-05-22 05:32

MSDN's "Service Identity and Authentication" explains that the endpoint identity section enables a client-side security precaution against phishing schemes.

From MSDN:

After the client initiates a communication to an endpoint and the service authenticates itself to the client, the client compares the endpoint identity value with the actual value the endpoint authentication process returned. If they match, the client is assured it has contacted the expected service endpoint. This functions as a protection against phishing by preventing a client from being redirected to an endpoint hosted by a malicious service.

Also see MSDN's "Service Identity Sample".

查看更多
登录 后发表回答