Sunday, March 29, 2015

Interoperability : WCF Secured Service, .NET Client, SoapUI, Java Client

Hi Guys,

I had a requirement to make a secured WCF service to be hosted in HTTPS and the credentials must be passed with the soap header within the username token tags. So this post is about that.

1) WCF Service

 public class MyService : IMyService
    {
   
        public ReturnMsg SaveTransaction(Transaction objTransaction, Patient objPatient)
         {
             System.Data.OracleClient.OracleConnection con = new System.Data.OracleClient.OracleConnection();
             System.Data.OracleClient.OracleCommand cmd = new System.Data.OracleClient.OracleCommand();
             ReturnMsg objReturnMsg = new ReturnMsg() { QaidNo = -1 };
            

            //The Logics have been removed 
             try
             {
                 //Validate for Mandatory Fields
                 if (Validate(objTransaction, objPatient, objReturnMsg))
                 {
                     

                     objReturnMsg.QaidNo = 10000;

                     objReturnMsg.Status = Status.Success;
                 }
                 

             }
             catch (Exception ex)
             {
                 objReturnMsg.Status = Status.Failed;
                 objReturnMsg.Message = ex.Message;
             }
             finally
             {
                 if (con.State == ConnectionState.Open)
                     con.Close();

                 con.Dispose();
             }
             return objReturnMsg;



         }
}

2) WCF Configurations (Web.config)

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="defaultProfile">
          <dataContractSerializer maxItemsInObjectGraph="2147483646" />
          <serviceMetadata httpGetEnabled="true" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfService.UsernameValidator, WcfService" />
            <clientCertificate>
              <authentication certificateValidationMode="None" />
            </clientCertificate>
          </serviceCredentials>
        </behavior>
       
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="basic">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </basicHttpBinding>
     
    </bindings>
    <services>
      <service behaviorConfiguration="defaultProfile" name="WcfService.MyService">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basic" name="wsSecureService" contract="WcfService.IMyService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />

  </system.serviceModel>

3) Custom Credential Validator 

public class UsernameValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            // validate arguments
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            if (string.IsNullOrEmpty(password))
                throw new ArgumentNullException("password");

            // check if the user is not test
            if (userName.Trim().ToLower() != WcfService.Properties.Settings.Default.WSSE_USER.Trim().ToLower()
                || password.Trim().ToLower() != WcfService.Properties.Settings.Default.WSSE_PWD.Trim().ToLower())
                throw new SecurityTokenException("Unknown username or password");
        }

    }

4) I created a Self signed certificate in IIS and hosted my Service in HTTPS 
 How to Create a Self Signed Certificate

.NET Client (Console Application)

1) Add Service Reference to MyService

2) Paste the following code in Program.cs

static void Main(string[] args)
        {

            

            //Creating an instance for the client   
            
            ServiceReference.MyServiceClient client = new ServiceReference.MyServiceClient();
            //Setting the Credentials
            client.ClientCredentials.UserName.UserName = "username";
            client.ClientCredentials.UserName.Password = "password";

            try
            {

                //Creating and Filling the Objects which will be passed to method
                ClientApplication.ServiceReference.Transaction objTransaction = new ClientApplication.ServiceReference.Transaction();
                ClientApplication.ServiceReference.Patient objPatient = new ClientApplication.ServiceReference.Patient();

               
                ClientApplication.ServiceReference.ReturnMsg objReturnMsg = client.SaveTransaction(objTransaction, null);

                //If Status is Success
                if (objReturnMsg != null && objReturnMsg.Status == ServiceReference.Status.Success)
                    Console.WriteLine(objReturnMsg.QaidNo);
                else
                    Console.WriteLine(objReturnMsg.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally { 
                 Console.ReadLine();
            }            

        }

3) Output



Soap UI 

1) Create a New Soap Project with Soap UI for the hosted WCF service (WSDL)

2) Double click on the created the Request and PasswordText for WSS-Password Type, specify the correct username and password in Request Properties

3) Output 


Java Client

1) Create a Java Application using Net Beans

2) Add a Web Service Client for the Java Application 

3) Add a SecurityHandler.java class for the project and paste the following code. Here you will specify the username and password.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package serviceclient;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.handler.MessageContext;
import javax.xml.soap.*;
import java.util.*;
import javax.xml.namespace.QName;

/**
 *
 * @author Sshahim
 */
public final class SecurityHandler implements SOAPHandler<SOAPMessageContext> {

    @Override public Set<QName> getHeaders()
    {
         QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", 
                "Security"); 
        HashSet<QName> headers = new HashSet<QName>(); 
        headers.add(securityHeader);         
        return headers; 
    }
    
    @Override public void close(MessageContext context)
    {
        
    }
    
    @Override public boolean handleFault(SOAPMessageContext context)
    {
        return true;
    }

    @Override  public boolean handleMessage(final SOAPMessageContext msgCtx) {

        // Indicator telling us which direction this message is going in
        final Boolean outInd = (Boolean) msgCtx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        // Handler must only add security headers to outbound messages
        if (outInd.booleanValue()) {
            try {
                // Get the SOAP Envelope
                final SOAPEnvelope envelope = msgCtx.getMessage().getSOAPPart().getEnvelope();

                // Header may or may not exist yet
                SOAPHeader header = envelope.getHeader();
                if (header == null)
                    header = envelope.addHeader();

                // Add WSS Usertoken Element Tree 
                final SOAPElement security = header.addChildElement("Security", "wsse",
                        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                final SOAPElement userToken = security.addChildElement("UsernameToken", "wsse");
                userToken.addChildElement("Username", "wsse").addTextNode("username");
                userToken.addChildElement("Password", "wsse").addTextNode("password");

            } catch (final Exception e) {
                
                return false;
            }
        }
        return true;
    }

    
    // Other required methods on interface need no guts
}

4) Open the main java file and paste the following code. This code will bind the created handler before calling the service.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package serviceclient;

import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.datacontract.schemas._2004._07.wcfservice.ReturnMsg;
import javax.xml.ws.BindingProvider;
import com.sun.xml.wss.XWSSConstants;
import javax.xml.ws.Binding;
import javax.xml.soap.SOAPException;
import java.util.*;
import javax.xml.ws.handler.Handler;
import org.datacontract.schemas._2004._07.wcfservice.Status;

/**
 *
 * @author Sshahim
 */
public class ServiceClient {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        
        org.datacontract.schemas._2004._07.wcfservice.Transaction objTransaction =
                new org.datacontract.schemas._2004._07.wcfservice.Transaction();
        
        org.datacontract.schemas._2004._07.wcfservice.Patient objPatient = 
                new org.datacontract.schemas._2004._07.wcfservice.Patient();
        
       ReturnMsg objReturn = saveTransaction(objTransaction, objPatient);
       if (objReturn.getStatus() == Status.SUCCESS)
           System.out.println(objReturn.getQaidNo()); 
       else
           System.out.println(objReturn.getMessage().getValue());  
        
                 
        
    }

    

    private static ReturnMsg saveTransaction(org.datacontract.schemas._2004._07.wcfservice.Transaction objTransaction, org.datacontract.schemas._2004._07.wcfservice.Patient objPatient) {
          
       

        org.tempuri.MyService service = new org.tempuri.MyService();
        org.tempuri.IMyService port = service.getWsSecureService();
        


        final Binding binding = ((BindingProvider)port).getBinding();
        List<Handler> handlerList = binding.getHandlerChain();
        if (handlerList == null)
            handlerList = new ArrayList<Handler>();

        handlerList.add(new SecurityHandler());
        binding.setHandlerChain(handlerList); // <- important!
     
        
        return port.saveTransaction(objTransaction, objPatient);
    }
    
    
    
}

5) Output