Wednesday, 18 May 2016

Web Service Client with Basic Authentication and SSL

Web Service Client with Basic Authentication and SSL

Recently, I had to create a web service client for a web service that uses a number of Web Service Policies. In general, the web service utilizes the following policies:
  • Transport: Service uses one way certificates. Client had to download and check server´s certificate in order to prove the server´s  identity.
  • Authentication: Basic authentication is required to access the URL and the service WSDL.
The following steps were used.
  • Creating the TrustStore: Access the Web Service URL, download the web service certificate and create a x509 trustStore to host the server´s certificate.
  • Create the client Stub: Access the Web Service URL and create the client stub by compiling the WSDL with wsimport.
  • Code and complete the service client. This has the following sub tasks:
    • Code the client to use Basic Authentication
    • Code the client to utilize the trustStore in order to setup SSL session with the server
    • Code the client to call the web method.

Creating the SSL Trustore.

During SSL handshake, the trustStore is used to verify server´s id.
Download the Server´s certificate by hitting the Web Service URL. There you will be prompted for login. You can login with the given user/password.

Then, the certificate is stored in your browser. You can export it easy but that depends to you browser. Chrome for example, the certificate can be downloaded directly as a x509 trustStore like the following image illustrates:



If you want to create the a trustStore manually you need to create a X509 keystore file using Java keytool and then import the server´s public certificate in it. The trustStore will be password protected and the certificate inside the trustStore will be password protected using "password" passphrase:

$ keytool -genkey -alias replserver -keyalg RSA -keystore mykeystore.jks -dname "cn=localhost, ou=IT, o=Continuent, c=DE"  -storepass password -keypass password

Now you have the keyStore. Next you need to import the server´s public certificate in it. In the general case, supposing the Server certificate is the following one plain text file server-certificate.txt then do one of the following actions to:

Check the server´s certificate:
openssl x509 -in server-certificate.txt -text -noout

Delete previous certificate version from the trustStore if any:
keytool -delete -alias myserver-name.com  -keystore mykeystore.jks 

Re-import the server certificate to the trustStore:
keytool -import -alias myserver-name.com -keystore mykeystore.jks  -file server-certificate.txt

Access the Web Service URL and create the client stub by compiling the WSDL with wsimport.

After running your wsimport command directly you should get a message complaining about a missing web authorization file.
What you need to do is create an authorization file (usually the default name/location for it is $HOME_DIRECTORY/.metro/auth, but check the previous error message, you'll get the hint from there).
Inside this file you just write the line: "https://username:password@url?wsdl"

 Now create a file called: wsimport_mysvc.bat and code the following commands:
setlocal
set _JAVA_OPTIONS=%_JAVA_OPTIONS% -Djavax.net.ssl.trustStore=mykeystore.jks -Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.trustStore=mykeystore.jks
wsimport -s . -verbose -keep -p gr.illumine.wsclient.stub  -extension https://myserver-name.com/wsd/alc_interface?wsdl
endlocal

Doing so, you fulfill both conditions for basic authentication and also for transport/SSL by asking wsimport to examine what is been sent from server against to what is stored in mykeystore.jks

Run the wsimport_mysvc.bat and the client stub files will be created in the package gr.illumine.wsclient.stub

C:\>set _JAVA_OPTIONS= -Djavax.net.ssl.trustStore=cacerts -Djavax.
net.ssl.keyStorePassword=changeit -Djavax.net.ssl.trustStore=cacerts

C:\>wsimport -s . -verbose -keep -p gr.illumine.wsclient.stub  -extension https://myserver-name.com/wsd/alc_interface?wsdl
Picked up _JAVA_OPTIONS:  -Djavax.net.ssl.trustStore=cacerts -Djavax.net.ssl.key
StorePassword=changeit -Djavax.net.ssl.trustStore=cacerts
parsing WSDL...

Code the client

The first thing you have to do is to add a static initializer that will provide the username and password for basic authentication:

public class AlcClient {
 
 private static final Logger log= Logger.getLogger( AlcClient.class.getName() );
 
 /* 
  * Use this static initializer to provide Basic Authentication for the Web Service Consumption
  */
 static {
     java.net.Authenticator.setDefault(new java.net.Authenticator() {

         @Override
         protected java.net.PasswordAuthentication getPasswordAuthentication() {
             return new java.net.PasswordAuthentication("happyuser", "mypassword".toCharArray());
         }
     });
 }

Next, configure your SSL settings in the code, by adding the following system parameters:
        /*
         * Use the following settings to specify how this client will utilize the X509 trust store
         * called mykeystore.jks. In this trustore, it is stored the server´s public certificate
         * Also the trustore/keystores are password protected with a password "password"
         */
        System.setProperty("java.protocol.handler.pkgs","com.sun.net.ssl.internal.www.protocol");
        System.setProperty("javax.net.ssl.keyStore","mykeystore.jks");
        System.setProperty("javax.net.ssl.keyStorePassword","password");
        System.setProperty("javax.net.ssl.keyStoreType", "JKS");
        System.setProperty("javax.net.ssl.trustStore","mykeystore.jks");
        System.setProperty("javax.net.ssl.trustStorePassword","password");
        System.setProperty("javax.net.ssl.trustStoreType", "JKS");

Then add some debugging options to debug your SSL session. You are strongly advised to comment out the following code after testing it since it will affect the SSL performance.
        /* Following options enable logging of all communication to the console
         * We are most interested in the request response SOAP Messages   */
        System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
        System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");
        System.setProperty("com.sun.xml.ws.transport.http.HttpAdapter.dump", "true");
        System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dump", "true");

Now code the Web Service client instance by using the stub you have created with the wsimport:
 ZALCINTERFACE_Service service = new ZALCINTERFACE_Service( new URL("myserver-name.com/wsd/alc_interface?wsdl"),
     new QName("urn:com:myserver-name:document:sap:soap:functions:mc-style", 
                      "ZALC_INTERFACE"));
  
 /*
  * From this service get the proper port
 */
 ZALCINTERFACE port = service.getZALCINTERFACE(); 

        /* Make the web service call */
        String responseMessage = port.callMyWebMethod();

Get the entire web service client java implementation can be downloaded here

No comments: