web-dev-qa-db-fra.com

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

J'ai le code suivant en cours d'exécution dans un service Windows:

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

À chaque fois, je reçois l'exception suivante

{"The remote server returned an error: (401) Unauthorized."}

À l'exception intérieure suivante:

{"The function requested is not supported"}

Je sais pour sûr que les informations d'identification sont valides, en fait, si je vais à downloadUrl dans mon navigateur Web et que je mets mes informations d'identification en tant que evilcorp.com\me avec le mot de passe 12345, cela se télécharge correctement.

Ce qui est étrange cependant, c'est que si je spécifie mes informations d'identification en tant que [email protected] avec 12345, cela semble échouer.

Existe-t-il un moyen de formater les informations d'identification?

32
Matt

Apparemment, le système d'exploitation que vous exécutez est important, car le cryptage par défaut a changé entre les systèmes d'exploitation. Ce blog contient plus de détails: http://ferozedaud.blogspot.com/2009/10/ntlm-auth-fails-with.html

Cela a apparemment également été discuté sur stackoverflow ici: 407 Authentification requise - aucun défi envoyé

Je suggère de lire le blog en premier car les connaissances distillées sont là.

6
Aryabhatta

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

82
P.Brian.Mackey

Pour moi, 'webClient.UseDefaultCredentials = true;' le résout uniquement sur local, 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 trouvé plus tard un moyen de programmation - je ne le testerai pas car j'ai déjà fait ma propre solution. Et je ne veux pas non plus 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 sur celui-ci (par exemple .NET).

La solution pour moi était donc assez simple: 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, pas en exécutant le code intégré dans Windows. J'ai moi-même choisi Node.js et la bibliothèque httpntlm , car il s'agit d'un seul fichier source unique avec quelques lignes et de l'appel depuis .NET en tant que programme renvoyant le fichier téléchargé (je préfère également le transférer via le 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 = 'User';
var password = 'Password';
var domain = 'Domain';

var file = process.argv.slice(2);           // file to download as a parameter

httpntlm.get({
    url: 'https://server/folder/proxypage.aspx?filename=' + file,
    username: login,
    password: password,
    workstation: '',
    domain: domain,
    binary: true                            // don't forget for binary files
}, function (err, res/*ponse*/) {
    if (err) { 
        console.log(err);
    } else {
        if (res.headers.location) {         // in my case, the server redirects to a similar URL,
            httpntlm.get({                  // now containing the session ID
                url: 'https://server' + res.headers.location,
                username: login,
                password: password,
                workstation: '',
                domain: domain,
                binary: true                // don't forget for binary files
            }, 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)                                             // to binary file
                              return console.log("Error writing file");
                          console.log("434980.png saved");
                      });*/
                      console.log(res.body.toString('base64'));  // didn't find a way to output
                }                                                // binary file, toString('binary')
            });                                                  // is not enough (docs say it's
                                                                 // just 'latin1')...
        } else {           // if there's no redirect
            //console.log(res.headers);                          // ...so I output base64 and
            console.log(res.body.toString('base64'));            // convert it back in the caller 
        }                                                        // code
    }
});

Code d'appelant .NET (l'application Web téléchargeant des fichiers à partir d'une 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 installs into the PATH
        psi.Arguments = "MyProxyDownladProgram.js " + 
            path.Replace("the base URL before the file name", "");
        psi.WorkingDirectory = "C:\\Folder\\With My\\Proxy Download Program";
        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);             // wait up to 60 minutes 
            if (p.ExitCode != 0)
                throw new Exception("Exit code: " + p.ExitCode);
        }
        finally
        {
            p.Close();
            p.Dispose();
        }
        // convert the outputted base64-encoded string to binary data
        return System.Convert.FromBase64String(System.Text.Encoding.Default.GetString(output));
    }
    else
    {
        return System.IO.File.ReadAllBytes(path);
    }
}
2
Ladislav Zima

Selon les msdn docs l'exception pourrait être parce que la méthode a été appelée simultanément sur plusieurs threads. La méthode DownloadFile requiert également une URL complètement qualifiée telle que http://evilcorp.com/.

2
jac