HttpClient 4.1.1 возвращает 401 при аутентификации с NTLM, браузеры работают нормально - PullRequest
11 голосов
/ 07 мая 2011

Я пытаюсь использовать 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. Дайджест тоже подойдет, но я могу без этого обойтись.

Ответы [ 6 ]

9 голосов
/ 04 марта 2014

Я не эксперт в данной области, но во время аутентификации NTLM с использованием компонентов http я увидел, что клиенту требуется 3 попытки для подключения к конечной точке NTML в моем случае.Это как бы описано здесь для Spnego, но немного отличается для аутентификации NTLM.

Для NTLM в первой попытке клиент сделает запрос с Target auth state: UNCHALLENGED, и веб-сервер вернет HTTP401 статус и заголовок: 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();
5 голосов
/ 18 ноября 2013

У меня была такая же проблема с 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();
3 голосов
/ 22 октября 2011

У меня была похожая проблема с HttpClient 4.1.2.Для меня это было решено путем возврата к HttpClient 4.0.3.Я никогда не мог заставить NTLM работать с 4.1.2, используя встроенную реализацию или JCIFS.

2 голосов
/ 22 апреля 2016

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

2 голосов
/ 11 мая 2011

Самый простой способ устранения подобных ситуаций - Wireshark .Это очень большой молоток, но он действительно покажет вам все.Установите его, убедитесь, что ваш сервер находится на другом компьютере (не работает с Localhost), и запустите ведение журнала.

Запустите ваш запрос, который не выполнен, запустите тот, который работает.Затем выполните фильтрацию по http (просто введите http в поле фильтра), найдите первый запрос GET, найдите другой запрос GET и сравните.Определите значимую разницу, теперь у вас есть конкретные ключевые слова или проблемы для поиска кода / сети.Если этого недостаточно, остановитесь на первом разговоре по TCP и посмотрите полный запрос / ответ.То же самое с другим.

Я решил невероятное количество проблем с этим подходом.И Wireshark - очень полезный инструмент для понимания.Множество супер-продвинутых функций для упрощения отладки вашей сети.

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

1 голос
/ 26 мая 2011

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

...