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 заставило его работать.