38 votes

WebClient génère une erreur (401) Non autorisé

J'ai le code suivant qui s'exécute dans un service Windows :

WebClient webClient = new WebClient();
webClient.Credentials = new NetworkCredential("me", "12345", "evilcorp.com");
webClient.DownloadFile(downloadUrl, filePath);

Chaque fois, j'obtiens l'exception suivante

{"Le serveur distant a renvoyé une erreur : (401) Non autorisé."}

Avec l'exception interne suivante :

{"La fonction demandée n'est pas prise en charge"}

Je sais avec certitude que les identifiants sont valides, en fait, si je vais sur l'URL de téléchargement dans mon navigateur web et que je saisis mes identifiants en tant que evilcorp.com\me avec le mot de passe 12345, le téléchargement se fait correctement.

Ce qui est étrange cependant, c'est que si je spécifie mes identifiants en tant que me@evilcorp.com avec 12345, cela semble échouer.

Y a-t-il un moyen de formater les identifiants?

95voto

P.Brian.Mackey Points 12892

webClient.UseDefaultCredentials = true; a résolu mon problème.

6voto

Apparemment, l'OS que vous utilisez a de l'importance, car le cryptage par défaut a changé entre les OS.

Ce blog contient plus de détails: http://ferozedaud.blogspot.com/2009/10/ntlm-auth-fails-with.html

Apparemment, cela a également été discuté sur stackoverflow ici: 407 Authentication required - no challenge sent

Je recommanderais de lire d'abord le blog car les connaissances essentielles s'y trouvent.

2voto

jac Points 5599

Selon les documents de msdn, l'exception pourrait être due au fait que la méthode a été appelée simultanément sur plusieurs threads. La méthode DownloadFile nécessite également une URL complètement qualifiée telle que http://evilcorp.com/.

2voto

Ladislav Zima Points 99

Pour moi, 'webClient.UseDefaultCredentials = true;' ne résout le problème que localement, pas dans l'application web sur le serveur se connectant à un autre serveur. Je n'ai pas pu ajouter les informations d'identification nécessaires dans Windows en tant qu'utilisateur, mais j'ai ensuite trouvé une méthode de programmation - je ne la testerai pas car j'ai déjà trouvé ma propre solution. De plus, je ne veux pas manipuler le registre du serveur Web, même si j'ai les droits d'administrateur nécessaires. Tous ces problèmes sont dus à la gestion interne de Windows de l'authentification NTLM ("Domaine Windows") et de toutes les bibliothèques et frameworks construits par-dessus (par exemple .NET).

La solution pour moi était assez simple en théorie - créer une application proxy dans une technologie multiplateforme avec une bibliothèque NTLM multiplateforme où la communication NTLM est créée à la main selon les spécifications publiques, et non en exécutant le code intégré dans Windows. J'ai choisi Node.js et la bibliothèque httpntlm, car il s'agit d'un seul fichier source avec quelques lignes et l'appeler depuis .NET en tant que programme renvoyant le fichier téléchargé (je préfère également le transférer via la sortie standard au lieu de créer un fichier temporaire).

Programme Node.js en tant que proxy pour télécharger un fichier derrière l'authentification NTLM :

var httpntlm = require('httpntlm');         // https://github.com/SamDecrock/node-http-ntlm
//var fs = require('fs');

var login = 'Utilisateur';
var password = 'Mot de passe';
var domain = 'Domaine';

var file = process.argv.slice(2);           // fichier à télécharger en tant que paramètre

httpntlm.get({
    url: 'https://server/dossier/pageproxy.aspx?nomfichier=' + file,
    username: login,
    password: password,
    workstation: '',
    domain: domain,
    binary: true                            // n'oubliez pas pour les fichiers binaires
}, function (err, res/*ponse*/) {
    if (err) { 
        console.log(err);
    } else {
        if (res.headers.location) {         // dans mon cas, le serveur redirige vers une URL similaire,
            httpntlm.get({                  // maintenant contenant l'ID de session
                url: 'https://serveur' + res.headers.location,
                username: login,
                password: password,
                workstation: '',
                domain: domain,
                binary: true                // n'oubliez pas pour les fichiers binaires
            }, function (err, res) {
                if (err) { 
                    console.log(err);
                } else {
                      //console.log(res.headers);
                      /*fs.writeFile("434980.png", res.body, function (err) {  // test write
                          if (err)                                             // au fichier binaire
                              return console.log("Erreur d'écriture du fichier");
                          console.log("434980.png enregistré");
                      });*/
                      console.log(res.body.toString('base64'));  // je n'ai pas trouvé de moyen de sortir
                }                                                // un fichier binaire, toString('binary')
            });                                                  // n'est pas suffisant (la doc dit que c'est
                                                                 // juste 'latin1')...
        } else {           // s'il n'y a pas de redirection
            //console.log(res.headers);                          // ...alors je sors en base64 et
            console.log(res.body.toString('base64'));            // le convertir à nouveau dans le code appelant
        }                                                        // code
    }
});

Code appelant .NET (l'application Web téléchargeant des fichiers à partir d'une autre application Web sur un autre serveur) :

public static string ReadAllText(string path)
{
    if (path.StartsWith("http"))
        return System.Text.Encoding.Default.GetString(ReadAllBytes(path));
    else
        return System.IO.File.ReadAllText(path);
}

public static byte[] ReadAllBytes(string path)
{
    if (path.StartsWith("http"))
    {
        ProcessStartInfo psi = new ProcessStartInfo();
        psi.FileName = "node.exe";                     // Node.js s'installe dans le PATH
        psi.Arguments = "MyProxyDownladProgram.js " + 
            path.Replace("l'URL de base avant le nom du fichier", "");
        psi.WorkingDirectory = "C:\\Dossier\\Avec Mon\\Programme de Téléchargement Proxy";
        psi.UseShellExecute = false;
        psi.CreateNoWindow = true;
        psi.RedirectStandardInput = true;
        psi.RedirectStandardOutput = true;
        psi.RedirectStandardError = true;
        Process p = Process.Start(psi);
        byte[] output;
        try
        {
            byte[] buffer = new byte[65536];
            using (var ms = new MemoryStream())
            {
                while (true)
                {
                    int read = p.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length);
                    if (read <= 0)
                        break;
                    ms.Write(buffer, 0, read);
                }
                output = ms.ToArray();
            }
            p.StandardOutput.Close();
            p.WaitForExit(60 * 60 * 1000);             // attendre jusqu'à 60 minutes 
            if (p.ExitCode != 0)
                throw new Exception("Code de sortie : " + p.ExitCode);
        }
        finally
        {
            p.Close();
            p.Dispose();
        }
        // convertir la chaîne base64 encodée en sortie en données binaires
        return System.Convert.FromBase64String(System.Text.Encoding.Default.GetString(output));
    }
    else
    {
        return System.IO.File.ReadAllBytes(path);
    }
}

0voto

Gerard ONeill Points 319

Hmm. Beaucoup de réponses, mais je me demande si répondre à votre dernière question aurait résolu tout. "me" n'est pas un type d'autorisation (à moins que votre serveur n'ait ajouté le support, bien sûr !). Vous voulez probablement "Basic".

Gardez également à l'esprit que certains services Web exigent que vous envoyiez l'en-tête d'autorisation lors de la requête initiale, et cela ne le fera pas. Plutôt, il répondra avec cela après avoir reçu une réponse d'autorisation requise du serveur. Si vous en avez besoin, vous devez créer votre propre en-tête d'autorisation.

String basicToken = Base64Encoding.EncodeStringToBase64(String.Format("{0}:{1}", clientId.Trim(), clientSecret.Trim()));

webClient.Headers.Add("Authorization", String.Format("Basic {0}", basicToken));

Et bien sûr, comme l'ont souligné certaines personnes, définir UseDefaultCredentials sur true fonctionne si vous utilisez IIS (ou un autre serveur http sécurisé par Windows) dans un environnement Windows.

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