Подтвердить что ты не робот

Ошибка Java URLConnection с аутентификацией ntlm, но только на Linux и только на Java 7

Я пытаюсь открыть http-соединение с URL-адресом, защищенным с помощью схемы аутентификации NTLM. Этот код работает правильно в течение 2 лет, когда мы были на Java 6. Я написал небольшую программу java, которая обращается к этому конкретному URL-адресу, чтобы сделать тестовый пример максимально простым.

Проблема в том, что я не могу заставить программу работать в Linux и при использовании версий JDK 7. Java пытается 20 раз получить доступ к URL-адресу, а затем я получаю сообщение об ошибке, указывающее, что сервер перенаправлен слишком много раз. Он отлично работает с linux и JDK 6, а в Windows 7 с JDK 6 или 7.

Я проверил и попробовал приведенное здесь решение (и многие другие): Получение "java.net.ProtocolException: сервер перенаправлен слишком много раз" Ошибка. Это не сработало. Я также должен добавить, что при доступе к URL-адресу из браузера я вижу, что куки файлы не задействованы.

Вот точная информация о версиях os/java, которые я пробовал:

Success

  • Windows 7: среда выполнения Java (TM) SE (сборка 1.7.0_15-b03) (64-разрядная версия)
  • Windows 7: среда выполнения Java (TM) SE (сборка 1.7.0_10-b18) (64-разрядная версия)
  • Windows 7: среда выполнения Java (TM) SE (сборка 1.6.0_33-b04) (64-разрядная версия)
  • Redhat enterprise linux 6.4: среда выполнения Java (TM) SE (сборка 1.6.0_33-b04) (64-разрядная версия)

Сбой:

  • Redhat enterprise linux 6.4: среда выполнения Java (TM) SE (сборка 1.7.0-b147) (64-разрядная версия)
  • Redhat enterprise linux 6.4: среда выполнения Java (TM) SE (сборка 1.7.0_05-b06) (64-разрядная версия)
  • Redhat enterprise linux 6.4: среда выполнения Java (TM) SE (сборка 1.7.0_13-b20) (64-разрядная версия)
  • Redhat enterprise linux 6.4: среда выполнения Java (TM) SE (сборка 1.7.0_15-b03) (64-разрядная версия)

Когда программа работает, я вижу используемые методы проверки подлинности и документ, который я пытаюсь загрузить в качестве вывода:

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

Когда он терпит неудачу, у меня есть следующий вывод:

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)

Вот исходный код программы:

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

Любая помощь будет принята с благодарностью.

UPDATE:

После еще нескольких исследований я обнаружил, что аутентификация работает, если я использую пользователя домена, но не если я использую локального пользователя.

Этот код из JDK 7 вызывает у меня проблемы (класс 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;
}

Итак, поскольку сервер зарегистрирован в домене, он отправляет клиенту его домен как часть протокола NTLM. Java заменяет домен, который я пытаюсь принудительно изменять с помощью переменной "domainFromServer" каждый раз, и он терпит неудачу, поскольку пользователь существует на сервере, а не на домене сервера.

Я точно не знаю, что с этим делать.

4b9b3361

Ответ 1

Я изменил код в классе Client.java и перекомпилировал его вместе с остальным пакетом com.sun.security.ntlm, затем создал банку rt_fix.jar, которая содержит классы этого конкретного пакета. Затем я использовал параметр запуска java, чтобы заставить его загружать мою банку перед внутренним rt.jar.

-Xbootclasspath/р:/path_to_jar/rt_fix.jar

Мне не нравится это решение, но оно сработало.

Вот код, который я изменил в Client.java, в методе type3:

До:

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

После:

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

Это не позволяет Java изменять домен, к которому я пытаюсь выполнить аутентификацию, с тем, который был получен с сервера при отправке третьей части проверки подлинности NTLM. Домен, который я пытался аутентифицировать, на самом деле является именем сервера, поскольку учетные записи пользователей являются локальными.

Ответ 2

У меня была такая же проблема, и я решил ее просто, указав имя пользователя с доменом, включенным в него:

    Authenticator.setDefault(new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(
                    System.getProperty("DOMAIN\\user"),
                    System.getProperty("password").toCharArray() ) ;
        }
    });

Ответ 3

Правильно это:

Authenticator.setDefault(new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(
                    "DOMAIN\\user",
                    "password".toCharArray() ) ;
        }
    });