Android: NTLM Authentication, ksoap, and persistent connections
Asked Answered
T

1

2

After working with iOS and dealing with auth challenges without much of a learning curve, I've found that Windows Authentication is much more complicated of a process in Java/Android.

I tried multiple different approaches, so without getting too much into those, I will get to the one that worked for the most part. I'm now using the class created for NTLM and ksoap called NtlmTransport

I'm now successfully authenticating in the following way:

NtlmTransport httpTransport = new NtlmTransport();
            httpTransport.setCredentials(serverURL, Login.username, Login.password, deviceIp, "DOMAINNAME");
            httpTransport.call(SOAP_ACTION, envelope);

If you take a look at the NtlmTransport class, you'll see that it's returning the following headers from the setupNtlm():

  • status Line HTTP/1.1 200 OK
  • Setup Cache-Control:private, max-age=0
  • Setup Content-Type:text/html; charset=utf-8
  • Setup Server:Microsoft-IIS/8.0
  • Setup X-AspNet-Version:4.0.30319
  • Setup Persistent-Auth:true
  • Setup X-Powered-By:ASP.NET
  • Setup Date:Tue, 17 Sep 2013 20:57:45 GMT
  • Setup Content-Length:11549

The "Persistent-Auth:true is the main one I'm concerned about at this time. I'm getting the SoapObjects just fine and can get the data I need from that one connection, but as soon as I try to access the web service again, which is presumably able to be hit after the successful authentication, I can't access a different method using HttpTransportSE:

private void setSomething() {

    xml = null;
    final String SOAP_ACTION = "http://this.ismy.org/AWebServiceMethod";
    final String METHOD_NAME = "AWebServiceMethod";
    final String URL = protocol + "://" + host  + ":" + port + "/WebService.asmx";
    SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
    SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
    envelope.dotNet = true;
    envelope.setOutputSoapObject(request);
    envelope.implicitTypes = true;
    envelope.setAddAdornments(false);

    try
    {
        HttpTransportSE transport = new HttpTransportSE(URL);
        transport.debug = true;
        transport.call(SOAP_ACTION, envelope);
        xml = transport.responseDump.toString();
        Log.d(TAG, xml);
    }
    catch(SocketException ex)
    {
        Log.e("SocketException : " , "Error on setSomething() " + ex.getMessage());
    }
    catch (Exception e)
    {
        Log.e("Exception : " , "Error on setSomething() " + e.getMessage());
    }
}

This all works just fine as the background task of an AsyncTask, which then passes the "xml" to an XMLPullParser method.

The main question here is why am I getting a:

Error on setSomething() No authentication challenges found

??

After IIS successfully validates the user with a 200, why is it asking me to authenticate again? How can I persist that first authenticated challenge to hit whatever method I want inside WebService.asmx? What are the headers that need to be added/changed to create a session if necessary? What am I missing that makes this whole NTLM process work and persist for more than the WS method that needs to pass the authentication challenges?

EDIT : Adding the Library code

Here's the link to the JCIFS from Apache

public static final class JCIFSEngine implements NTLMEngine {

    private static final int TYPE_1_FLAGS =
            NtlmFlags.NTLMSSP_NEGOTIATE_56 |
                    NtlmFlags.NTLMSSP_NEGOTIATE_128 |
                    NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 |
                    NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
                    NtlmFlags.NTLMSSP_REQUEST_TARGET;

    public String generateType1Msg(final String domain, final String workstation)
            throws NTLMEngineException {
        final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);
        return jcifs.util.Base64.encode(type1Message.toByteArray());
    }

    public String generateType3Msg(final String username, final String password,
                                   final String domain, final String workstation, final String challenge)
            throws NTLMEngineException {
        Type2Message type2Message;
        try {
            type2Message = new Type2Message(jcifs.util.Base64.decode(challenge));
        } catch (final IOException exception) {
            throw new NTLMEngineException("Invalid NTLM type 2 message", exception);
        }
        final int type2Flags = type2Message.getFlags();
        final int type3Flags = type2Flags
                & (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
        final Type3Message type3Message = new Type3Message(type2Message, Login.password, "",
                Login.username, deviceIp, type3Flags);

            System.out.println("type3Message: " + type3Message.toByteArray());

        return jcifs.util.Base64.encode(type3Message.toByteArray());
    }
}

So is the "NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN" causing this problem? Is there another flag I'm supposed to set for the keep-alive? Also, I found a great resource for a list of NTLM flags and more: http://fossies.org/dox/jcifs-1.3.17/interfacejcifs_1_1ntlmssp_1_1NtlmFlags.html

Turner answered 17/9, 2013 at 22:18 Comment(4)
No one out there knows how to authenticate using NTLM then use kSOAP after successful authentication?Turner
Not 100% sure, but I think that when you authenticate, the server returns you a hash. Then, for future connections, you use this hash to keep the session. But, all the auth libraries I used just handle that for me. If the library you use is smart enough, it should just do it for you. Are you sure you use "the good way"? Some lib has an event like "OnAuthChallengeReceive".Fredericafrederich
@Fredericafrederich I added the JCIFS call I'm making..from what I've read this is about as good as it gets. "So is the "NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN" causing this problem? Is there another flag I'm supposed to set for the keep-alive?"Turner
Did you tried to just re authenticate each time you contact the server? Then, use a tool like Fiddler to see if the server ask for a complete re authentification each time, or if the lib if just smart enougth to resend the hash. Sorry, I don't have more idea... I'm not a NTLM specialist myself!Fredericafrederich
A
3

I was also struggling about windows authentication from Android. I found android-ntlm-master on https://github.com/masconsult/android-ntlm. Add this class as library in your project.

Change is in NtlmTransport.java class.I made change in call method of NtlmTransport class =>

      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;
    }
}

And below is the code how I make call:

    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();

It worked for me.

More you can find here: https://suhas1989.wordpress.com/2015/01/28/ntlm-authentication-in-android/

Araucania answered 8/12, 2014 at 13:35 Comment(5)
Can you add some explanation of what you changed and how it helped in order to give more context as to how your answer ties in to the question?Fingernail
It would be painful for folks to read through whole code to make out any differences, rather post only diff code..Smoodge
Hi all, I'm developing a new service to do NTLM authentication nafiux.com/wasp if I can help you please let me knowInmost
@Rachit, if this is working for you,please up vote the answerAraucania
@SuhasPatil .. are you having issues with API 30 and DefaultHttpClient? I was using the legacy apache jar and from api 28 to 30, I'm getting a verify error with CloseableHttpClient? Weird.. it's crashing my alpha builds!Turner

© 2022 - 2024 — McMap. All rights reserved.