Ошибка 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" каждый раз, и он терпит неудачу, поскольку пользователь существует на сервере, а не на домене сервера.
Я точно не знаю, что с этим делать.
Ответы
Ответ 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() ) ;
}
});
Ответ 4
Эта проблема была решена в JDK 9-ea с http://bugs.java.com/view_bug.do?bug_id=7150092, и исправление было перенесено в JDK 8u40 также с помощью http://bugs.java.com/view_bug.do?bug_id=8049690