NTLM Authentication in Android.

I was developing an Android app which uses SOAP library to call .Net web services. Web services were not accessible directly. Before calling web services user must get authenticated. Here I met with concept called Windows Authentication.

I searched on internet for  NTLM authentication in Android. I came up with using DefaultHttpClient class provided in  org.apache.http.impl.client package.

I was able to log in by using below code ,

 NtlmTransport httpTransport = new NtlmTransport();
 httpTransport.setCredentials(URL, USERNAME, PASSWORD, "","");
 SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
 envelope.dotNet = true;    
 envelope.implicitTypes = true;
 envelope.setOutputSoapObject(request);                
 httpTransport.call(PRODUCT_DETAILS_SOAP_ACTION, envelope);

But It was not possible for me to call complex web services which accept some parameters. I searched on internet but I didn’t get any solution for that.I was using android-ntlm-master library project which has NtlmTransport.java class in it.This class extends org.ksoap2.transport.Transport class.To see what happens inside I extracted this org.ksoap2.transport.Transport class from ksoap library.

This is a abstract class for both HttpTransportSE and NtlmTransport class. I did one change here in Transport class.I added one method call() which looks like this,

public void call(String targetNamespace, SoapEnvelope envelope) throws IOException, XmlPullParserException {
      this.call(targetNamespace, envelope, (List)null);
   }

Now Transport.java class looks like this:

package org.ksoap2.transport;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Proxy;
import java.util.List;
import org.ksoap2.SoapEnvelope;
import org.kxml2.io.KXmlParser;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;

public abstract class Transport {

   protected Proxy proxy;
   protected String url;
   protected int timeout;
   public boolean debug;
   public String requestDump;
   public String responseDump;
   private String xmlVersionTag;


   public Transport() {
      this.timeout = 20000;
      this.xmlVersionTag = "";
   }

   public Transport(String url) {
      this((Proxy)null, url);
   }

   public Transport(String url, int timeout) {
      this.timeout = 20000;
      this.xmlVersionTag = "";
      this.url = url;
      this.timeout = timeout;
   }

   public Transport(Proxy proxy, String url) {
      this.timeout = 20000;
      this.xmlVersionTag = "";
      this.proxy = proxy;
      this.url = url;
   }

   protected void parseResponse(SoapEnvelope envelope, InputStream is) throws XmlPullParserException, IOException {
      KXmlParser xp = new KXmlParser();
      xp.setFeature("http://xmlpull.org/v1/doc/features.html#process-namespaces", true);
      xp.setInput(is, (String)null);
      envelope.parse(xp);
   }

   protected byte[] createRequestData(SoapEnvelope envelope) throws IOException {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      bos.write(this.xmlVersionTag.getBytes());
      KXmlSerializer xw = new KXmlSerializer();
      xw.setOutput(bos, (String)null);
      envelope.write(xw);
      xw.flush();
      bos.write(13);
      bos.write(10);
      bos.flush();
      return bos.toByteArray();
   }

   public void setUrl(String url) {
      this.url = url;
   }

   public void setXmlVersionTag(String tag) {
      this.xmlVersionTag = tag;
   }

   public void reset() {
   }

   public abstract List call(String var1, SoapEnvelope var2, List var3) throws IOException, XmlPullParserException;

   public void call(String targetNamespace, SoapEnvelope envelope) throws IOException, XmlPullParserException {
      this.call(targetNamespace, envelope, (List)null);
   }

   public abstract String getHost();

   public abstract int getPort();

   public abstract String getPath();
}
and NtlmTransport.java class:
package com.example.ksoapdemo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeFactory;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.auth.NTLMScheme;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.transport.ServiceConnection;
import org.ksoap2.transport.Transport;
import org.xmlpull.v1.XmlPullParserException;
import org.apache.http.HttpEntity;
import eu.masconsult.android_ntlm.JCIFSEngine;

public class NtlmTransport extends Transport {

    static final String ENCODING = "utf-8";

    private final DefaultHttpClient client = new DefaultHttpClient();
    private final HttpContext localContext = new BasicHttpContext();
    private String urlString;
    private String user;
    private String password;
    private String ntDomain;
    private String ntWorkstation;

    public void setCredentials(String url, String user, String password,
                               String domain, String workStation) {
        this.urlString = url;
        this.user = user;
        this.password = password;
        this.ntDomain = domain;
        this.ntWorkstation = workStation;

    }

    public List call(String targetNamespace, SoapEnvelope envelope, List headers)
            throws IOException, XmlPullParserException {
        return call(targetNamespace, envelope, headers, null);
    }

    public List call(String soapAction, SoapEnvelope envelope, List headers, File outputFile)
            throws IOException, XmlPullParserException {
        
        HttpResponse resp = null;
        try {
            //setupNtlm(urlString, user, password);  
             DefaultHttpClient httpclient = new DefaultHttpClient();
             httpclient.getAuthSchemes().register("ntlm", new NTLMSchemeFactory());
             httpclient.getCredentialsProvider().setCredentials(            
                    new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),               
                    new NTCredentials(user, password, "", "")
             );
             HttpPost httpget = new HttpPost(urlString);       
             httpget.addHeader("soapaction",  soapAction);        
             httpget.addHeader("Content-Type", "text/xml; charset=utf-8");
             byte[] requestData = null;
             try {
                 requestData = createRequestData(envelope);                 
             } catch (IOException iOException) {
             }
             ByteArrayEntity byteArrayEntity = new ByteArrayEntity(requestData);
             httpget.setEntity(byteArrayEntity);                
             resp = httpclient.execute(httpget); 

             if(resp  == null) {
                System.out.println("Response is null");
             }
             HttpEntity respEntity = resp.getEntity();

             InputStream is = respEntity.getContent();
             if(is == null) {
                System.out.println("InputStream is null");
             }
             parseResponse(envelope, is);

        } catch (Exception ex) {
            // ex.printStackTrace();
        }

        if (resp != null) {
            return Arrays.asList(resp.getAllHeaders());
        } else {
            return null;
        }
    }

    private void setHeaders(String soapAction, SoapEnvelope envelope, HttpPost httppost, List headers) {
        byte[] requestData = null;
        try {
            requestData = createRequestData(envelope);
        } catch (IOException iOException) {
        }
        ByteArrayEntity byteArrayEntity = new ByteArrayEntity(requestData);
        httppost.setEntity(byteArrayEntity);
        httppost.addHeader("User-Agent", "kSOAP/2.0");
        httppost.addHeader("SOAPAction", soapAction);
        httppost.addHeader("Content-Type", "text/xml; charset=utf-8");
        //org.ksoap2.transport.Transport.USER_AGENT);
        // SOAPAction is not a valid header for VER12 so do not add
        // it
        // @see "http://code.google.com/p/ksoap2-android/issues/detail?id=67
      /*  if (envelope.version != SoapSerializationEnvelope.VER12) {
            httppost.addHeader("SOAPAction", soapAction);
        }

        if (envelope.version == SoapSerializationEnvelope.VER12) {
            httppost.addHeader("Content-Type", Transport.CONTENT_TYPE_SOAP_XML_CHARSET_UTF_8);
        } else {
            httppost.addHeader("Content-Type", Transport.CONTENT_TYPE_XML_CHARSET_UTF_8);
        }*/

        // Pass the headers provided by the user along with the call
        if (headers != null) {
            for (int i = 0; i < headers.size(); i++) {
                HeaderProperty hp = (HeaderProperty) headers.get(i);
                httppost.addHeader(hp.getKey(), hp.getValue());
                //System.out.println(hp.getKey()+":"+hp.getValue());
            }
        }
    }

    // Try to execute a cheap method first. This will trigger NTLM authentication
    public void setupNtlm(String dummyUrl, String userId, String password) {
        try {

            ((AbstractHttpClient) client).getAuthSchemes().register("ntlm", new NTLMSchemeFactory());

            NTCredentials creds = new NTCredentials(userId, password, ntWorkstation, ntDomain);
            client.getCredentialsProvider().setCredentials(AuthScope.ANY, creds);

            HttpGet httpget = new HttpGet(dummyUrl);

            HttpResponse response1 = client.execute(httpget, localContext);
            HttpEntity entity1 = response1.getEntity();

            Header[] hArray = response1.getAllHeaders();
            int size = hArray.length;
          /*  StatusLine status = response1.getStatusLine();
            System.out.println("SERVER STATUS CODE"+status.getStatusCode());*/
            for (int i = 0; i < size; i ++) {
                Header h = hArray[i];
               //System.out.println(h.getName()+":"+h.getValue());
                if (h.getName().equals("WWW-Authenticate")) {
                    entity1.consumeContent();
                    throw new Exception("Failed Authentication");
                }
            }
           /* StatusLine status = response1.getStatusLine();
            System.out.println("SERVER STATUS CODE"+status.getStatusCode());
            String responseBody = EntityUtils.toString(entity1);
            System.out.println("================================");
            System.out.println(responseBody);
            System.out.println("================================");*/
            entity1.consumeContent();
        } catch (Exception ex) {
            // swallow
        }
    }

        //NTLM Scheme factory
    private class NTLMSchemeFactory implements AuthSchemeFactory {
        public AuthScheme newInstance(final HttpParams params) {
        // see http://www.robertkuzma.com/2011/07/
        // manipulating-sharepoint-list-items-with-android-java-and-ntlm-authentication/
            return new NTLMScheme(new JCIFSEngine());
        }
    }

    public ServiceConnection getServiceConnection() throws IOException
    {
        throw new IOException("Not using ServiceConnection in transport");
    }

    public String getHost() {
        String retVal = null;
        try {
            retVal = new URL(url).getHost();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return retVal;
    }

    public int getPort() {
        int retVal = -1;
        try {
            retVal = new URL(url).getPort();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return retVal;
    }

    public String getPath() {
        String retVal = null;
        try {
            retVal = new URL(url).getPath();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return retVal;
    }
}

To call any web service from your app one just needs to write below code:

      SoapObject request = new SoapObject(NAMESPACE, PRODUCT_DETAILS_METHOD_NAME);
      request.addProperty("ListingID", Integer.parseInt(Product_ID));
      NtlmTransport httpTransport = new NtlmTransport();
      httpTransport.setCredentials(URL, USERNAME, PASSWORD, "","");
      SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
      envelope.dotNet = true;    
      envelope.implicitTypes = true;
      envelope.setOutputSoapObject(request);                
      httpTransport.call(PRODUCT_DETAILS_SOAP_ACTION, envelope);
      SoapObject response = (SoapObject) envelope.getResponse();

I know this is not a one time authentication. Each time before calling web service one need to use this code.I need to use NtlmTransport class. But it solved my issue.

Leave a comment