C# WebClient 生成 (401) 未授权错误

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2150161/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 23:56:03  来源:igfitidea点击:

WebClient generates (401) Unauthorized error

c#webclientdownload

提问by Matt

I have the following code running in a windows service:

我在 Windows 服务中运行以下代码:

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

Each time, I get the following exception

每次,我都会收到以下异常

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

With the following inner exception:

有以下内部例外:

{"The function requested is not supported"}

I know for sure the credentials are valid, in fact, if I go to downloadUrl in my web browser and put in my credentials as evilcorp.com\me with password 12345, it downloads fine.

我确信凭据是有效的,事实上,如果我在 Web 浏览器中转到 downloadUrl 并将我的凭据输入为 evilcorp.com\me,密码为 12345,它可以正常下载。

What is weird though is that if I specify my credentials as [email protected] with 12345, it appears to fail.

但奇怪的是,如果我将我的凭据指定为 [email protected] 和 12345,它似乎失败了。

Is there a way to format credentials?

有没有办法格式化凭据?

采纳答案by Matt

Apparently the OS you are running on matters, as the default encryption has changed between OSes. This blog has more details: http://ferozedaud.blogspot.com/2009/10/ntlm-auth-fails-with.html

显然,您运行的操作系统很重要,因为操作系统之间的默认加密已更改。这个博客有更多细节:http: //ferozedaud.blogspot.com/2009/10/ntlm-auth-fails-with.html

This has apparently also been discussed on stackoverflow here: 407 Authentication required - no challenge sent

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

I would suggest read the blog first as the distilled knowledge is there.

我建议先阅读博客,因为那里有精炼的知识。

回答by jac

According to the msdn docsthe exception could be because the method has been called simultaneously on multiple threads. The DownloadFilemethod also requires a completely qualified URL such as http://evilcorp.com/.

根据msdn 文档,异常可能是因为该方法已在多个线程上同时调用。该DownloadFile方法还需要一个完全合格的URL,例如http://evilcorp.com/

回答by P.Brian.Mackey

webClient.UseDefaultCredentials = true;resolved my issue.

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

回答by Ladislav Zima

For me, 'webClient.UseDefaultCredentials = true;' solves it only on local, not in the web app on the server connecting to another server. I couldn't add the needed credential into Windows as a user, but I found later some programming way - I won't test it as I already made own solution. And also I don't want to mangle with the web server's registry, even if I have the needed admin rights. All these problems are because of the Windows internal handling of the NTLM authentication ("Windows Domain") and all of libraries and frameworks built over that (e.g. .NET).

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

So the solution for me was quite simple in idea - create a proxy app in a multiplatform technology with a multiplatform NTLM library where the NTLM communication is created by hand according to the public specs, not by running the built-in code in Windows. I myself chose Node.js and the httpntlm library, because it's about only one single source file with few lines and calling it from .NET as a program returning the downloaded file (also I prefer transferring it through the standard output instead of creating a temporary file).

因此,对我来说,解决方案的想法非常简单 - 使用多平台 NTLM 库在多平台技术中创建代理应用程序,其中 NTLM 通信是根据公共规范手动创建的,而不是通过在 Windows 中运行内置代码。我自己选择了 Node.js 和httpntlm 库,因为它只有一个包含几行的源文件,并从 .NET 调用它作为返回下载文件的程序(我也更喜欢通过标准输出传输它而不是创建一个临时文件)文件)。

Node.js program as a proxy to download a file behind the NTLM authentication:

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 caller code (the web app downloading files from a web app on another server)

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

回答by Gerard ONeill

Hmm. Lots of answers, but I wonder if answering your last question would have solved everything. "me" is not an authorization type (unless your server has added support for it, of course!). You probably want "Basic".

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

Also keep in mind that some webservices require you to send the authorization header on the initial request, and this won't do that. Rather it responds with it after getting an authorization required response from the server. If you need this, you need to create your own Authorization header.

还请记住,某些 Web 服务要求您在初始请求中发送授权标头,而这不会这样做。而是在从服务器获得授权所需的响应后用它进行响应。如果你需要这个,你需要创建你自己的 Authorization 标头。

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

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

And of course as people have pointed out, setting UseDefaultCredentials to true works if you are using IIS (or other windows security aware http server) in a windows environment.

当然,正如人们所指出的,如果您在 Windows 环境中使用 IIS(或其他 Windows 安全感知 http 服务器),则将 UseDefaultCredentials 设置为 true 有效。