WebClient генерирует (401) несанкционированную ошибку

У меня есть следующий код, работающий в службе Windows:

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

Каждый раз я получаю следующее исключение

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

Со следующим внутренним исключением:

{"The function requested is not supported"}

Я точно знаю, что учетные данные действительны, на самом деле, если я зайду на страницу downloadUrl в своем веб-браузере и введу свои учетные данные как evilcorp.com\me с паролем 12345, все загрузится нормально.

Что странно, так это то, что если я укажу свои учетные данные как [email protected] с 12345, это, похоже, не удастся.

Есть ли способ отформатировать учетные данные?


person Matt    schedule 27.01.2010    source источник
comment
Вы когда-нибудь придумывали решение (код) для этой проблемы?   -  person Crash893    schedule 16.02.2012


Ответы (5)


Очевидно, ОС, на которой вы работаете, имеет значение, поскольку шифрование по умолчанию изменилось между ОС. В этом блоге есть более подробная информация: http://ferozedaud.blogspot.com/2009/10/ntlm-auth-fails-with.html

Это, по-видимому, также обсуждалось в stackoverflow здесь: 407 Аутентификация не требуется - не требуется отправлено

Я бы посоветовал сначала прочитать блог, так как там есть очищенные знания.

person Community    schedule 27.01.2010

webClient.UseDefaultCredentials = true; решил мою проблему.

person P.Brian.Mackey    schedule 13.09.2013
comment
В большинстве случаев этот параметр требуется, когда серверу необходимо получить идентификационную информацию пользователя, например веб-сайт ASP.Net, настраивающий проверку подлинности Windows. - person lzlstyle; 30.11.2013
comment
Брайан, если бы ты был ближе, клянусь, я бы поцеловал тебя прямо сейчас!! Сражались с этим часами и пропустили только эту настройку!? :-о СПАСИБО!! - person KDT; 04.12.2013
comment
Определенно выручил и меня. Спасибо, Брайан. - person FastTrack; 07.01.2015
comment
Почему это так? Разве это не означает, что WebClient будет использовать учетные данные того, кто запускает приложение? Это решение работает для меня, но я пишу это консольное приложение для кого-то другого и хочу, чтобы оно работало, когда его запускает другой пользователь, даже если у него самого могут не быть правильных разрешений. - person vwfreak; 09.10.2015

Согласно документам msdn, исключение может быть связано с тем, что метод был вызывается одновременно в нескольких потоках. Метод DownloadFile также требует полного URL-адреса, такого как http://evilcorp.com/.

person jac    schedule 27.01.2010

Для меня 'webClient.UseDefaultCredentials = true;' решает это только локально, а не в веб-приложении на сервере, подключающемся к другому серверу. Я не мог добавить необходимые учетные данные в Windows как пользователь, но позже я нашел какой-то способ программирования - я не буду его тестировать, так как я уже сделал собственное решение. А также я не хочу искажать реестр веб-сервера, даже если у меня есть необходимые права администратора. Все эти проблемы возникают из-за внутренней обработки Windows аутентификации NTLM («Домен Windows») и всех библиотек и фреймворков, созданных для этого (например, .NET).

Таким образом, решение для меня было довольно простым по идее - создать прокси-приложение в мультиплатформенной технологии с мультиплатформенной библиотекой NTLM, где связь NTLM создается вручную в соответствии с общедоступными спецификациями, а не путем запуска встроенного кода в Windows. Я сам выбрал Node.js и https://github.com/SamDecrock/node-http-ntlm, потому что речь идет только об одном источнике. файл с несколькими строками и вызывать его из .NET как программу, возвращающую загруженный файл (также я предпочитаю передавать его через стандартный вывод вместо создания временного файла).

Программа Node.js в качестве прокси для загрузки файла за аутентификацией 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
    }
});

Код вызывающей стороны .NET (веб-приложение загружает файлы из веб-приложения на другом сервере)

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);
    }
}
person Ladislav Zima    schedule 02.11.2018

Хм. Много ответов, но мне интересно, ответ на ваш последний вопрос решил бы все. "me" не является типом авторизации (если, конечно, ваш сервер не добавил его поддержку!). Вы, вероятно, хотите "Базовый".

Также имейте в виду, что некоторые веб-сервисы требуют, чтобы вы отправили заголовок авторизации при первоначальном запросе, и это не будет сделано. Скорее, он отвечает им после получения от сервера ответа, требующего авторизации. Если вам это нужно, вам нужно создать свой собственный заголовок авторизации.

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

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

И, конечно же, как указывали люди, установка для UseDefaultCredentials значения true работает, если вы используете IIS (или другой http-сервер, поддерживающий безопасность Windows) в среде Windows.

person Gerard ONeill    schedule 22.01.2020