WebClient生成(401)未授权错误

时间:2010-01-27 20:53:29

标签: c# webclient download

我在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"}

我肯定知道凭据是有效的,事实上,如果我在我的网络浏览器中下载urrl并输入我的凭据evilcorp.com \ me,密码为12345,则下载正常。

但奇怪的是,如果我将我的凭证指定为me@evilcorp.com 12345,它似乎会失败。

有没有办法格式化凭据?

6 个答案:

答案 0 :(得分:81)

webClient.UseDefaultCredentials = true;解决了我的问题。

答案 1 :(得分:6)

显然,您运行的操作系统很重要,因为默认加密在操作系统之间发生了变化。 此博客包含更多详细信息:http://ferozedaud.blogspot.com/2009/10/ntlm-auth-fails-with.html

这显然也在stackoverflow上进行了讨论:407 Authentication required - no challenge sent

我建议先阅读博客,因为那里有蒸馏知识。

答案 2 :(得分:2)

根据msdn docs,异常可能是因为该方法已在多个线程上同时调用。 DownloadFile 方法还需要完全限定的网址,例如 http://evilcorp.com/

答案 3 :(得分:2)

对我来说,“ webClient.UseDefaultCredentials = true;”仅在本地解决此问题,而不是在连接到另一台服务器的服务器上的Web应用程序中解决。我无法以用户身份将所需的凭据添加到Windows中,但是后来我发现了一些编程方式-因为我已经制定了自己的解决方案,所以我不会对其进行测试。而且,即使我具有所需的管理员权限,我也不想mangle with the web server's registry。所有这些问题是由于Windows内部对NTLM身份验证(“ Windows域”)进行处理,以及由于该问题而构建的所有库和框架(例如.NET)的原因。

因此,对我而言,解决方案的想法非常简单-使用具有多平台NTLM库的多平台技术创建代理应用,其中NTLM通信是根据公共规范手动创建的,而不是通过运行内置代码视窗。我自己选择了Node.js和the httpntlm library,因为它仅是一个单一的源文件,只有几行,并作为返回下载文件的程序从.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调用者代码(该Web应用程序从另一台服务器上的Web应用程序下载文件)

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);
    }
}

答案 4 :(得分:0)

嗯。很多答案,但我想知道回答您的最后一个问题是否能解决所有问题。 “我”不是授权类型(当然,除非您的服务器已添加对它的支持!)。您可能需要“基本”。

还请记住,某些Web服务需要您在初始请求上发送授权标头,而这样做不会这样做。相反,它会在从服务器获取授权要求的响应后做出响应。如果需要,则需要创建自己的Authorization标头。

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

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

当然,正如人们指出的那样,如果您在Windows环境中使用IIS(或其他具有Windows安全意识的HTTP服务器),请将UseDefaultCredentials设置为true即可。

答案 5 :(得分:-2)

在编写URL时,将“@”放在url字符串前面。

例如:

var url = @"http://evilcorp.com";
WebClient webClient = new WebClient();
webClient.Proxy = null;
webClient.DownloadFile(new Uri(url), filePath);

这将解决您的问题。

P.Brian.Mackey给出的答案也是正确的。