HttpClient 4.1.1 возвращает 401 при аутентификации с NTLM, браузеры работают нормально
Я пытаюсь использовать Apache / Jakarta HttpClient 4.1.1 для подключения к произвольной веб-странице с использованием заданных учетных данных. Чтобы проверить это, у меня есть минимальная установка IIS 7.5 на моей машине dev, где одновременно активен только один режим проверки подлинности. Базовая аутентификация работает нормально, но Digest и NTLM возвращают сообщения об ошибках 401 всякий раз, когда я пытаюсь войти в систему. Вот мой код:
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost/");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("user", "password", "", "localhost"));
if (!new File(System.getenv("windir") + "krb5.ini").exists()) {
List<String> authtypes = new ArrayList<String>();
authtypes.add(AuthPolicy.NTLM);
authtypes.add(AuthPolicy.DIGEST);
authtypes.add(AuthPolicy.BASIC);
httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF,
authtypes);
httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF,
authtypes);
}
localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
HttpResponse response = httpclient.execute(httpget, localContext);
System.out.println("Response code: " + response.getStatusLine());
единственное, что я заметил в Fiddler, это то, что хэши, отправленные Firefox против HttpClient отличаются, заставляя меня думать, что, возможно, IIS 7.5 ожидает более сильного хэширования, чем HttpClient? Есть идеи? Было бы здорово, если бы я мог проверить, что это будет работать с NTLM. Дайджест тоже был бы хорош, но я могу обойтись и без него, если понадобится.
6 ответов
Я не эксперт по этому вопросу, но во время аутентификации NTLM с использованием http-компонентов я видел, что клиенту нужно 3 попытки для подключения к конечной точке NTML в моем случае. Это своего рода описано здесь для Spnego, но это немного отличается для аутентификации NTLM.
для NTLM с первой попытки клиент сделает запрос с Target auth state: UNCHALLENGED
и веб-сервер возвращает статус HTTP 401 и заголовок:WWW-Authenticate: NTLM
клиент проверит для настроенных схем проверки подлинности NTLM должен быть настроен в клиентском коде.
вторая попытка, клиент сделает запрос с Target auth state: CHALLENGED
, и отправит заголовок авторизации с токеном, закодированным в формате base64:Authorization: NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==
Сервер снова возвращает статус HTTP 401, но заголовок:WWW-Authenticate: NTLM
теперь заполняется закодированной информации.
3-я попытка клиент будет использовать информацию из WWW-Authenticate: NTLM
заголовок и сделает окончательный запрос с Target auth state: HANDSHAKE
и заголовок авторизации Authorization: NTLM
который содержит больше информации для сервера.
в моем случае я получаю HTTP/1.1 200 OK
после этого.
чтобы избежать всего этого в каждом запросе документация в главе 4.7.1 говорится, что один и тот же токен выполнения должен использоваться для логически связанных запросов. Для меня это не сработало.
мой код:
Я инициализирую клиента один раз в @PostConstruct
метод EJB
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(18);
cm.setDefaultMaxPerRoute(6);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(30000)
.setConnectTimeout(30000)
.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM))
.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
.build();
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new NTCredentials(userName, password, hostName, domainName));
// Finally we instantiate the client. Client is a thread safe object and can be used by several threads at the same time.
// Client can be used for several request. The life span of the client must be equal to the life span of this EJB.
this.httpclient = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultCredentialsProvider(credentialsProvider)
.setDefaultRequestConfig(requestConfig)
.build();
используйте то же самое экземпляр клиента в каждом запросе:
HttpPost httppost = new HttpPost(endPoint.trim());
// HttpClientContext is not thread safe, one per request must be created.
HttpClientContext context = HttpClientContext.create();
response = this.httpclient.execute(httppost, context);
освободить ресурсы и вернуть соединение обратно в диспетчер соединений, в методе @PreDestroy моего EJB:
this.httpclient.close();
у меня была такая же проблема с HttpClient4.1.X после обновления до HttpClient 4.2.6 он проснулся как шарм. Ниже приведен мой код
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("url");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("username", "pwd", "", "domain"));
List<String> authtypes = new ArrayList<String>();
authtypes.add(AuthPolicy.NTLM);
httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF,authtypes);
localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
HttpResponse response = httpclient.execute(httpget, localContext);
HttpEntity entity=response.getEntity();
У меня была аналогичная проблема с HttpClient 4.1.2. Для меня это было решено путем возврата к HttpClient 4.0.3. Я никогда не мог заставить NTLM работать с 4.1.2, используя встроенную реализацию или используя JCIFS.
самый простой способ устранения таких ситуаций я нашел Wireshark. Это очень большой молоток, но он действительно покажет вам все. Установить его, убедитесь, что ваш сервер находится на другой машине (не работает с localhost) и начать ведение журнала.
запустите запрос, который не выполняется, запустите тот, который работает. Затем фильтруйте по http (просто поместите http в поле фильтра), найдите первый запрос GET, найдите другой запрос GET и сравните. Определите значимую разницу, вы сейчас есть определенные ключевые слова или проблемы для поиска кода / сети. Если недостаточно, сузьте до первого разговора TCP и посмотрите на полный запрос/ответ. То же самое с другим.
Я решить невероятное количество проблем с этим подходом. И Wireshark очень полезный инструмент, чтобы знать. Множество супер-продвинутых функций, чтобы упростить отладку сети.
вы также можете запустить его на клиент или сервер. Что бы показать вам как запросы позволяют сравнивать.
обновление нашего приложения для использования банок в httpcomponents-client-4.5.1 решило эту проблему для меня.
Я, наконец, понял это. Дайджест-проверка подлинности требует, чтобы при использовании полного URL-адреса в запросе прокси-сервер также использовал полный URL-адрес. Я не оставил прокси-код в образце, но он был направлен на "localhost", что вызвало его сбой. Изменение этого на 127.0.0.1 заставило его работать.