views:

340

answers:

1

Hi

I've developed a very simple host and client which I wanted to use to test whether it would be possible for a WCF client to pass the logged on windows' user's credentials to the host service without requiring the user to re-enter their credentials or setup security.

My host config looks like this:

<configuration>
  <system.serviceModel>
    <services>
      <service name="WCFTest.CalculatorService" behaviorConfiguration="WCFTest.CalculatorBehavior">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8000/WCFTest/CalculatorService/" />
            <add baseAddress = "net.tcp://localhost:9000/WCFTest/CalculatorService/" />
          </baseAddresses>
        </host>
        <endpoint address ="basicHttpEP" binding="basicHttpBinding" contract="WCFTest.ICalculatorService" bindingConfiguration="basicHttpBindingConfig"/>
        <endpoint address ="netTcpEP" binding="netTcpBinding" contract="WCFTest.ICalculatorService"/>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>

      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="basicHttpBindingConfig">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WCFTest.CalculatorBehavior">          
          <serviceAuthorization impersonateCallerForAllOperations="false"  principalPermissionMode="UseWindowsGroups" />
          <serviceCredentials >
            <windowsAuthentication allowAnonymousLogons="false" includeWindowsGroups="true" />
          </serviceCredentials>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

My client config looks like this:

<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="WCFClient.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_ICalculatorService" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    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" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
            <netTcpBinding>
                <binding name="NetTcpBinding_ICalculatorService" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
                    hostNameComparisonMode="StrongWildcard" listenBacklog="10"
                    maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10"
                    maxReceivedMessageSize="65536">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <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="http://ldndwm286380:8000/WCFTest/CalculatorService/basicHttpEP"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICalculatorService"
                contract="CalcService.ICalculatorService" name="BasicHttpBinding_ICalculatorService" />
            <endpoint address="net.tcp://ldndwm286380:9000/WCFTest/CalculatorService/netTcpEP"
                binding="netTcpBinding" bindingConfiguration="NetTcpBinding_ICalculatorService"
                contract="CalcService.ICalculatorService" name="NetTcpBinding_ICalculatorService">
            </endpoint>
        </client>
    </system.serviceModel>
    <userSettings>
        <WCFClient.Settings1>
            <setting name="Setting" serializeAs="String">
                <value>True</value>
            </setting>
        </WCFClient.Settings1>
    </userSettings>
</configuration>

My client code is:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Press enter to start");
        Console.ReadLine();

        CalcService.ICalculatorService httpCalcService = new CalcService.CalculatorServiceClient("BasicHttpBinding_ICalculatorService");
        Console.WriteLine(httpCalcService.AddValues(new int[] { 1, 2, 3 }).Value);
        Console.ReadLine();

        CalcService.ICalculatorService TcpCalcService = new CalcService.CalculatorServiceClient("NetTcpBinding_ICalculatorService");
        Console.WriteLine(httpCalcService.AddValues(new int[] { 5, 10, 15 }).Value);
        Console.ReadLine();
    }
}

This works fine if I run the client on my PC. If I run the client on a colleague's PC I get this exception stack on the client:

Unhandled Exception: System.ServiceModel.Security.MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate oX0we6ADCgEBonQEcm BwBgkqhkiG9xIBAgIDAH5hMF+gAwIBBaEDAgEepBEYDzIwMTAwMzExMTAzNjAzWqUFAgMEDFimAwIBKakYGxZJTlRSQU5FVC5CQVJDQVBJTlQuQ09NqhowGK ADAgEBoREwDxsNTERORFdNMjg2MzgwJA=='. ---> System.Net.WebException: The remote server returned an error: (401) Unauthoriz ed. ---> System.ComponentModel.Win32Exception: The target principal name is incorrect at System.Net.NTAuthentication.GetOutgoingBlob(Byte[] incomingBlob, Boolean throwOnError, SecurityStatus& statusCode)

at System.Net.NTAuthentication.GetOutgoingBlob(String incomingBlob) at System.Net.NegotiateClient.DoAuthenticate(String challenge, WebRequest webRequest, ICredentials credentials, Boole an preAuthenticate) at System.Net.NegotiateClient.Authenticate(String challenge, WebRequest webRequest, ICredentials credentials) at System.Net.AuthenticationManager.Authenticate(String challenge, WebRequest request, ICredentials credentials) at System.Net.AuthenticationState.AttemptAuthenticate(HttpWebRequest httpWebRequest, ICredentials authInfo) at System.Net.HttpWebRequest.CheckResubmitForAuth() at System.Net.HttpWebRequest.CheckResubmit(Exception& e) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.GetResponse() at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeou t) --- End of inner exception stack trace ---

Any help appreciated,

Thanks,

Nick

+1  A: 

Within the configuration for your client, you'll need to add an additional element within your 'endpoint' element to specify the principal that the service is running under. Here is an example:

<identity>
      <servicePrincipalName value="[email protected]" />
</identity>

This should allow for proper authentication to occur when establishing a channel between your client and host. If the service is being run under a different account, you will need to adjust the value accordingly (i.e. if it is running as a windows service using the system account, it would be similar to 'host/mymachine.mydomain.com').

MattK
Thanks very much - can this be set programatically?
ng5000
There is a property called 'Identity' within an EndpointAddress object. You can set this to an SpnEndpointIdentity object which takes the name of principal you are using.
MattK