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.