web-dev-qa-db-fra.com

Authentification C # WebClient NTLM à partir de chaque demande

Considérons une simple application C # NET Framework 4.0, qui:

  • utilise WebClient
  • s'authentifie à l'aide de NTLM (testé sur IIS 6.0 et IIS 7.5)
  • récupère plusieurs fois une chaîne d'une URL à l'aide de DownloadString ()

Voici un exemple qui fonctionne bien:

using System;
using System.Net;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string URL_status = "http://localhost/status";

            CredentialCache myCache = new CredentialCache();
            myCache.Add(new Uri(URL_status), "NTLM", new NetworkCredential("username", "password", "domain"));

            WebClient WebClient = new WebClient();
            WebClient.Credentials = myCache;

            for (int i = 1; i <= 5; i++)
            {
                string Result = WebClient.DownloadString(new Uri(URL_status));
                Console.WriteLine("Try " + i.ToString() + ": " + Result);
            }

            Console.Write("Done");
            Console.ReadKey();
        }
    }
}

Le problème:

Lors de l'activation du traçage, je constate que l'authentification NTLM ne persiste pas.

Chaque fois que Webclient.DownloadString est appelé, l'authentification NTLM démarre (le serveur renvoie l'en-tête "WWW-Authenticate: NTLM" et l'ensemble du processus d'authentification/autorisation se répète; il n'y a pas d'en-tête "Connection: close").

NTLM n'était-il pas censé authentifier une connexion, pas une demande?

Existe-t-il un moyen de faire en sorte que WebClient réutilise une connexion existante pour éviter d'avoir à ré-authentifier chaque demande?

15
c4n

Après 10 jours à essayer tout ce à quoi je pouvais penser et à apprendre beaucoup dans le processus, j'ai finalement trouvé une solution à ce problème.

L'astuce consiste à activer UnsafeAuthenticatedConnectionSharing en remplaçant GetWebRequest et en définissant la propriété sur true dans le HttpWebRequest que vous récupérez.

Vous souhaiterez peut-être combiner cela avec la propriété ConnectionGroupName pour éviter l'utilisation potentielle de la connexion par des applications non authentifiées.

Voici l'exemple de la question modifiée pour fonctionner comme prévu. Il ouvre une seule connexion authentifiée NTLM et la réutilise:

using System;
using System.Net;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string URL_status = "http://localhost/status";

            CredentialCache myCache = new CredentialCache();
            myCache.Add(new Uri(URL_status), "NTLM", new NetworkCredential("username", "password", "domain"));

            MyWebClient webClient = new MyWebClient();
            webClient.Credentials = myCache;

            for (int i = 1; i <= 5; i++)
            {
                string result = webClient.DownloadString(new Uri(URL_status));
                Console.WriteLine("Try {0}: {1}", i, result);
            }

            Console.Write("Done");
            Console.ReadKey();
        }
    }

    public class MyWebClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {
            WebRequest request = base.GetWebRequest(address);

            if (request is HttpWebRequest) 
            {
                var myWebRequest = request as HttpWebRequest;
                myWebRequest.UnsafeAuthenticatedConnectionSharing = true;
                myWebRequest.KeepAlive = true;
            }

            return request;
        }
    }   
}

À ce stade, je voudrais également remercier @ Falco Alexander pour toute l'aide; alors que ses suggestions ne fonctionnaient pas tout à fait pour moi, il m'a indiqué la bonne direction à rechercher et a finalement trouvé la réponse.

19
c4n

Vérifiez le paramètre de votre IIS, bien que cela devrait être par défaut.

<windowsAuthentication
   enabled="True"
   authPersistSingleRequest="False"
   UseKernelMode>

</windowsAuthentication>  

Réf: https://msdn.Microsoft.com/en-us/library/aa347472.aspx

Avez-vous vérifié la zone dans laquelle se trouve votre hôte local IIS? C'était également un piège du côté client dans le passé lorsque vous travailliez avec WinInet. Vérifiez le comportement par défaut de WebClient.


Éditer:

Après avoir reproduit l'erreur, je pourrais comprendre que c'est l'implémentation NTLM de préauthentification de WebClient qui vous empêche d'une seule requête 401:

var WebClient = new PreAuthWebClient();
WebClient.Credentials = new NetworkCredential("user", "pass","domain");

//Do your GETs 

Public class PreAuthWebClient: WebClient
{
    protected override WebRequest GetWebRequest (Uri address)
    {
        WebRequest request = (WebRequest) base.GetWebRequest (address);
        request.PreAuthenticate = true;
        return request;
  }
}
2
Falco Alexander