views:

2928

answers:

1

I am consuming a web service internal to my company from ASP.NET. I've used svcutil.exe to connect to the service and generate the bindings and classes from the wsdl. I am able to connect to the dev version, which does not require authentication. Now we are adding in security. My new URI uses https but also requires user credentials.

I am very new to WCF, and am trying to determine the way to configure this. From my reading on MSDN, it appears that the way to go is using.

UPDATE: Here is the most recent code I've been trying. This incorporates feedback from the answer(s):

 <system.serviceModel>
   <behaviors>
     <serviceBehaviors>
       <behavior name="svcBehavior">
         <serviceCredentials>
           <serviceCertificate storeLocation="CurrentUser"
                               storeName="My"
                               x509FindType="FindByThumbprint"
                               findValue="xx xx xx etc"/>
         </serviceCredentials>
       </behavior>
     </serviceBehaviors>
   </behaviors>
   <bindings>
     <wsHttpBinding>
       <binding name="CustomerPaymentProgramSOAPBinding">
         <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" />
         </security>
       </binding>
     </wsHttpBinding>
   </bindings>
  <client>
   <endpoint address="https://***URL***"
    binding="wsHttpBinding" bindingConfiguration="CustomerPaymentProgramSOAPBinding"
    contract="CppService.CustomerPaymentProgramService" name="CustomerPaymentProgramService">
   </endpoint>
  </client>
 </system.serviceModel>

Here is the calling code:

using (var svc = new CustomerPaymentProgramServiceClient())
{
    svc.ClientCredentials.UserName.UserName = "*******";
    svc.ClientCredentials.UserName.Password = "*******";
    var request = new GetServiceDataProgramRequest()
          {
           CustomerAccountId = Convert.ToInt64(customerAccountId)
          };

    svc.Open();
    var response = new GetServiceDataProgramResponse();
    var metaData = new RequestMetadata()
           {
            ClientIPAddress = "xx.xx.xx.xx",
            TrackingNumber = "1",
            UserID = "1"
           };

    svc.GetAccountData(metaData, request, out response);
}

I am getting an error stating I am passing anonymous credentials with the request. With the updated code, now I receive a different exception:

UPDATE:

After making suggested changes as well as removing the service call from the using block (per this article), I'm now getting a MessageSecurityException:

Error message:
-$exception {"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. 
The authentication header received from the server was 'Basic realm=\"Spring Security Application\"'."} 
System.Exception {System.ServiceModel.Security.MessageSecurityException}


Server stack trace: 
at System.ServiceModel.Channels.HttpChannelUtilities.ValidateAuthentication(HttpWebRequest request, HttpWebResponse response, WebException responseException, HttpChannelFactory factory)
at System.ServiceModel.Channels.HttpChannelUtilities.ValidateRequestReplyResponse(HttpWebRequest request, HttpWebResponse response, HttpChannelFactory factory, WebException responseException)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.DoOperation(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionClientSettings`1.ClientSecuritySessionChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
at System.ServiceModel.Channels.ServiceChannel.EnsureOpened(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)\r\n\r\nException rethrown at [0]: 
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at [ServiceName].GetAccountData(svcRequest request)
at [ServiceName].GetAccountData(GetAccountDataRequest request) 
    in c:\\[Project]\\service references\\[ServiceName]\\reference.cs:line 3480
at c:\\[Project]\\service references\\[ServiceName](RequestMetadata RequestMetadata, ServiceRequest, ServiceResponse& ServiceResponse) 
    in c:\\[Project]\\service references\\[ServiceName]\\reference.cs:line 3487
at c:\\[Project]\\service references\\[ServiceName].CheckAccountForPaymentPlan(String customerAccountId) 
    in c:\\[Project]\\service references\\[ServiceName]\\\\PlanCheckService.cs:line 32
+4  A: 

With TransportWithMessageCredential, you're specifying that you'll use message security to secure the individual messages, which requires the client credentials (username and password). You need something similar to the config from the msdn link as below:

<wsHttpBinding>
<binding name="WsHttpBinding_ICalculator">
        <security mode="TransportWithMessageCredential" >
           <message clientCredentialType="UserName" />
        </security>
</binding>
</wsHttpBinding>

This doesn't require specifying a certificate for client credentials. Transport security (using https and a valid ssl certificate) works in the same way a web site does, it doesn't require additional credentials/certs from the user. Valid certs from trusted certificate authorities are installed on the server (client machines are able to validate them) and the handshake process secures the channel. This does not require you to set clientCrdentials in config. You just need to install a valid cert (or test cert for dev) and configure the server config to point to it with something similar to:

<behaviors>
 <serviceBehaviors>
   <behavior name="mySvcBehavior">
       <serviceCredentials>
         <serviceCertificate findValue="contoso.com"
                             x509FindType="FindByIssuerName" />
       </serviceCredentials>
   </behavior>
 </serviceBehaviors>
</behaviors>

Try to remove the < transport clientCredentialType="Certificate" /> from your server config as a starter, update service refs and ensure your cert is working and configured correctly. Post your actual exceptions and more config if you still have problems.

For a good WCF source try: CodePlex, it helped me out no end when i started with WCF. The different application scenarios provide useful checklists to help ensure you don't miss any steps in your configuration.

Good Luck

UPDATE:

Once a channel is faulted, it needs to be recreated as you won't be able to communicate with the service until it's been reset. So add a check to recreate it: If svc.State = CommunicationState.Faulted Then....

Try remove the svc.Open() line as I've never actually used that. I checked msdn for usage details but got about 2 lines of useless info. Once the service is setup you should be able to communicate with it without having to open it specifically. Not sure that this will actually make a difference though?!

Other things to check: - can you right click on the service and view in browser without problems? - in IIS can you view the certificate in directory security without any issues? - debug up to the point before the service call is made and check the credentials are correctly assigned. - check server event viewer for any info it may have logged with the request (if it's getting that far).

Also, here's some of the exception I trap to determine issues using ex.GetBaseException.GetType:

ServiceModel.EndpointNotFoundException

  • server connection problem - either IIS isn't running or invalid server name

ServiceModel.Security.MessageSecurityException

  • Base Exception - ServiceModel.FaultException

    • "FailedAuthentication" - Bad credentials entered by user
    • "InvalidSecurity" - DB Error - either account has no access to DB, DB name in web config is incorrect, user password has expired in the database
    • "InvalidRequest" - Certificate accessibility issue - check service account has access to certificate
  • Base Exception - Net.WebException

    • Unauthorized access 401 - Check anonymous access in IIS is turned on
  • Base Exception - IdentityModel.Tokens.SecurityTokenValidationException

    • No service certificate is assigned in IIS
  • Base Exception - System.ServiceModel.CommunicationException

    • Identity mismatch with server certificate - eg in dev environment, cert named "localhost" so if you enter PC number or IP address for service you'll see this
Tanner
@Tanner, thanks for the recommendations. I posted my most recent results above. Now I'm getting a different exception. Any ideas?
Mark Struzinski
Again, thanks for the feedback. Now I'm over to a MessageSecurityException. BTW - I don't have any control over the server side. It's a java service with an associated .wsdl file. I'm only writing the client end to consume it. Any other feedback? Thanks again for your help!
Mark Struzinski