12 votes

HttpClient 4.1.1 retourne 401 lors de l'authentification avec NTLM, les navigateurs fonctionnent correctement.

Je suis en train d'essayer d'utiliser l'HttpClient 4.1.1 d'Apache/Jakarta pour me connecter à n'importe quelle page web en utilisant les identifiants donnés. Pour tester cela, j'ai une installation minimale de IIS 7.5 sur ma machine de développement où un seul mode d'authentification est actif à la fois. L'authentification de base fonctionne bien, mais Digest et NTLM renvoient des messages d'erreur 401 chaque fois que j'essaie de me connecter. Voici mon code :

    DefaultHttpClient httpclient = new DefaultHttpClient();
    HttpContext localContext = new BasicHttpContext();
    HttpGet httpget = new HttpGet("http://localhost/");
    CredentialsProvider credsProvider = new BasicCredentialsProvider();
    credsProvider.setCredentials(AuthScope.ANY,
            new NTCredentials("utilisateur", "motdepasse", "", "localhost"));
    if (!new File(System.getenv("windir") + "\\krb5.ini").exists()) {
        List authtypes = new ArrayList();
        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("Code de réponse : " + response.getStatusLine());

La seule chose que j'ai remarquée dans Fiddler est que les hachages envoyés par Firefox par rapport à ceux envoyés par HttpClient sont différents, me faisant penser que peut-être IIS 7.5 attend un hachage plus fort que celui fourni par HttpClient? Des idées? Ce serait super si je pouvais vérifier que cela fonctionnerait avec NTLM. Digest serait aussi bien, mais je peux m'en passer si nécessaire.

10voto

gazgas Points 39

Je ne suis pas un expert sur le sujet mais lors de l'authentification NTLM en utilisant les composants http, j'ai vu que le client avait besoin de 3 tentatives pour se connecter à un point de terminaison NTML dans mon cas. C'est un peu décrit ici pour Spnego mais c'est un peu différent pour l'authentification NTLM.

Pour NTLM, lors de la première tentative, le client fera une demande avec État d'authentification cible : NON CONTESTÉ et le serveur Web renverra un statut HTTP 401 et un en-tête: WWW-Authenticate: NTLM

Le client vérifiera les schémas d'authentification configurés, NTLM doit être configuré dans le code client.

Lors de la deuxième tentative, le client fera une demande avec État d'authentification cible : CONTESTÉ, et enverra un en-tête d'autorisation avec un jeton encodé au format base64: Autorisation: NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw== Le serveur renvoie à nouveau un statut HTTP 401 mais l'en-tête: WWW-Authenticate: NTLM contient désormais des informations encodées.

Le client de la 3ème tentative utilisera les informations de l'en-tête WWW-Authenticate: NTLM et fera la demande finale avec État d'authentification cible : HANDSHAKE et un en-tête d'autorisation Autorisation: NTLM qui contient plus d'informations pour le serveur.

Dans mon cas, je reçois un HTTP/1.1 200 OK après cela.

Pour éviter tout cela à chaque demande, la documentation au chapitre 4.7.1 indique que le même jeton d'exécution doit être utilisé pour les demandes logiquement liées. Pour moi, ça n'a pas fonctionné.

Mon code: Je initialise le client une fois dans une méthode @PostConstruct d'un 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));

        // Enfin, nous instancions le client. Le client est un objet thread-safe et peut être utilisé par plusieurs threads en même temps. 
        // Le client peut être utilisé pour plusieurs demandes. La durée de vie du client doit être égale à la durée de vie de cet EJB.
         this.httpclient = HttpClients.custom()
        .setConnectionManager(cm)
        .setDefaultCredentialsProvider(credentialsProvider)
        .setDefaultRequestConfig(requestConfig)
        .build();

Utilisez la même instance de client à chaque demande:

            HttpPost httppost = new HttpPost(endPoint.trim());            
            // HttpClientContext n'est pas thread-safe, il faut en créer un par demande.
            HttpClientContext context = HttpClientContext.create();    
            response = this.httpclient.execute(httppost, context);

Désallouez les ressources et renvoyez la connexion au gestionnaire de connexions, à la méthode @PreDestroy de mon EJB:

             this.httpclient.close();

6voto

J'ai eu le même problème avec HttpClient4.1.X Après l'avoir mis à jour vers HttpClient 4.2.6, cela a fonctionné comme un charme. Voici mon code

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 authtypes = new ArrayList();
            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();

4voto

La manière la plus simple de résoudre de telles situations que j'ai trouvé est Wireshark. C'est un outil très puissant, mais il vous montrera réellement tout. Installez-le, assurez-vous que votre serveur est sur une autre machine (ne fonctionne pas avec Localhost) et commencez à enregistrer.

Exécutez votre requête qui échoue, exécutez-en une qui fonctionne. Ensuite, filtrez par http (il suffit de mettre http dans le champ de filtre), trouvez la première requête GET, trouvez l'autre requête GET et comparez. Identifiez les différences significatives, vous avez maintenant des mots-clés spécifiques ou des problèmes à rechercher dans le code/réseau. Si ce n'est pas suffisant, réduisez ensuite la conversation TCP initiale et examinez la demande/réponse complète. De même pour l'autre.

J'ai résolu un nombre incroyable de problèmes avec cette approche. Et Wireshark est un outil très utile à connaître. Beaucoup de fonctions super avancées pour faciliter le débogage de votre réseau.

Vous pouvez également l'exécuter soit côté client, soit côté serveur. Peu importe, cela vous montrera les deux requêtes pour permettre la comparaison.

3voto

Jeff Nadler Points 86

J'ai eu un problème similaire avec HttpClient 4.1.2. Pour moi, cela a été résolu en revenant à HttpClient 4.0.3. Je n'ai jamais pu faire fonctionner NTLM avec 4.1.2 en utilisant soit l'implémentation intégrée soit en utilisant JCIFS.

2voto

Andrew Plata Points 21

Mettre à jour notre application pour utiliser les jars dans httpcomponents-client-4.5.1 a résolu ce problème pour moi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X