HttpClient 4.1.1 возвращает 401 при проверке подлинности с NTLM, браузеры работают нормально



Я пытаюсь использовать Apache / Jakarta HttpClient 4.1.1 для подключения к произвольной веб-странице с использованием заданных учетных данных. Чтобы проверить это, у меня есть минимальная установка IIS 7.5 на моей машине разработки, работающей, где только один режим проверки подлинности активен одновременно. Обычная аутентификация работает нормально, но 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. Дайджест тоже был бы хорош, но я могу обойтись и без него, если понадобится.

667   6  

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-очень полезный инструмент, чтобы знать. Множество суперсовременных функций, облегчающих отладку сети.

Вы также можете запустить его на стороне клиента или сервера. Все, что покажет вам обе просьбы, позволит вам сравнить.

Обновление нашего приложения для использования jars в HTTP components-client-4.5.1 решило эту проблему для меня.

Я наконец-то понял это. Дайджест-проверка подлинности требует, чтобы при использовании полного URL-адреса в запросе прокси-сервер также использовал полный URL-адрес. Я не оставил прокси-код в образце, но он был направлен на "localhost", что вызвало его сбой. Изменив его на 127.0.0.1, он заработал.

Comments

    Ничего не найдено.