Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java URLConnection error with ntlm authentication, but only on Linux and only Java 7

I am trying to open an http connection to an url protected with the NTLM authentication scheme. This code has been working correctly for 2 year when we were on Java 6.I wrote a small java program which access that particular url to make the test case as simple as possible.

The problem is that I am unable to make the program work on linux and when using versions of the JDK 7. Java tries 20 times to access the URL and then I get an error telling me that the server redirected too many times. It works fine with linux and JDK 6, and in windows 7 with JDK 6 or 7.

I checked and tried the solution listed here (and many others) : Getting "java.net.ProtocolException: Server redirected too many times" Error. It didn't work. I also have to add that when accessing the url from a browser, I can see that there are no cookies involved.

Here is the exact detail of the os/java versions that I have tried :

Success:

  • Windows 7: Java(TM) SE Runtime Environment (build 1.7.0_15-b03) (64 bit)
  • Windows 7: Java(TM) SE Runtime Environment (build 1.7.0_10-b18) (64 bit)
  • Windows 7: Java(TM) SE Runtime Environment (build 1.6.0_33-b04) (64 bit)
  • Redhat enterprise linux 6.4: Java(TM) SE Runtime Environment (build 1.6.0_33-b04) (64 bit)

Fail:

  • Redhat enterprise linux 6.4: Java(TM) SE Runtime Environment (build 1.7.0-b147) (64 bit)
  • Redhat enterprise linux 6.4: Java(TM) SE Runtime Environment (build 1.7.0_05-b06) (64 bit)
  • Redhat enterprise linux 6.4: Java(TM) SE Runtime Environment (build 1.7.0_13-b20) (64 bit)
  • Redhat enterprise linux 6.4: Java(TM) SE Runtime Environment (build 1.7.0_15-b03) (64 bit)

When the program works, I see the authentication methods that were used and the document that I am trying to download as the output:

Scheme:Negotiate
Scheme:ntlm
.... document content ....
Done

When it fails, I have the following output :

Scheme:Negotiate
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
java.net.ProtocolException: Server redirected too many  times (20)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1635)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
        at TestWs.testWs(TestWs.java:67)
        at TestWs.main(TestWs.java:20)

Here is the source code of the program:

package com.test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;

public class TestWs {

    public static void main(String[] args) throws Exception {
        new TestWs().testWs();
    }

    public void testWs() {
        try {
            CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
            Authenticator.setDefault(new MyAuthenticator("username", "password"));

            URL url = new URL("https://someurlprotectedbyntlmauthentication.com");
            URLConnection connection = url.openConnection();
            InputStream is = connection.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            while (true) {
                String s = br.readLine();
                if (s == null)
                    break;
                System.out.println(s);
            }
            System.out.println("Done");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

class MyAuthenticator extends Authenticator {
    private String httpUsername;
    private String httpPassword;

    public MyAuthenticator(String httpUsername, String httpPassword) {
        this.httpUsername = httpUsername;
        this.httpPassword = httpPassword;
    }

    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
        System.out.println("Scheme:" + getRequestingScheme());
        return new PasswordAuthentication(httpUsername, httpPassword.toCharArray());
    }
}

Any help would be greatly appreciated.

UPDATE:

After some more investigation, I found that the authentication works if I use a domain user, but not if I use a local user.

This code from the JDK 7 causes me troubles (class com.sun.security.ntlm.Client) :

public byte[] type3(byte[] type2, byte[] nonce) throws NTLMException {
if (type2 == null || (v != Version.NTLM && nonce == null)) {
throw new NullPointerException("type2 and nonce cannot be null");
}
debug("NTLM Client: Type 2 received\n");
debug(type2);
Reader r = new Reader(type2);
byte[] challenge = r.readBytes(24, 8);
int inputFlags = r.readInt(20);
boolean unicode = (inputFlags & 1) == 1;
String domainFromServer = r.readSecurityBuffer(12, unicode);
if (domainFromServer != null) {
domain = domainFromServer;
}

So, since the server is enroled in a domain, it sends back to the client it's domain as part of the NTLM protocol. Java replaces the domain that I'm trying to force by the variable "domainFromServer" everytime and it fails since the user exists on the server and not on the server's domain.

I don't know exactly what to do with that.

like image 717
Yanick Avatar asked Feb 27 '13 22:02

Yanick


1 Answers

I changed code in the Client.java class, and recompiled it along with the rest of the com.sun.security.ntlm package, then I created a jar called rt_fix.jar which contains the classes of that particular package. Then I used a java startup option to force it to load my jar before the internal rt.jar.

-Xbootclasspath/p:/path_to_jar/rt_fix.jar

I don't like this solution, but it worked.

Here is the code I changed in Client.java, in the method type3:

Before :

    if (domainFromServer != null) {
        domain = domainFromServer;
    }

After :

    if (domainFromServer != null) {
        //domain = domainFromServer;
    }

It stops Java from altering the domain I try to authenticate to with the one recieved from the server when sending the 3rd part of the NTLM authentication. The domain I was trying to authenticate to is in fact the name of the server because the user accounts are local.

like image 157
Yanick Avatar answered Sep 29 '22 18:09

Yanick