简体   繁体   English

WebClient 生成 (401) 未授权错误

[英]WebClient generates (401) Unauthorized error

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 me@evilcorp.com with 12345, it appears to fail.但奇怪的是,如果我将我的凭据指定为 me@evilcorp.com 和 12345,它似乎失败了。

Is there a way to format credentials?有没有办法格式化凭据?

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

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.我建议先阅读博客,因为那里有精炼的知识。

According to the msdn docs the exception could be because the method has been called simultaneously on multiple threads.根据msdn 文档,异常可能是因为该方法已在多个线程上同时调用。 The DownloadFile method also requires a completely qualified URL such as http://evilcorp.com/ . DownloadFile方法还需要一个完全限定的 URL,例如http://evilcorp.com/

For me, 'webClient.UseDefaultCredentials = true;'对我来说,'webClient.UseDefaultCredentials = true;' solves it only on local, not in the web app on the server connecting to another server.仅在本地解决它,而不是在连接到另一台服务器的服务器上的 Web 应用程序中解决。 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.我无法以用户身份将所需的凭据添加到 Windows 中,但后来我发现了一些编程方式 - 我不会对其进行测试,因为我已经制定了自己的解决方案。 And also I don't want to mangle with the web server's registry , even if I have the needed admin rights.而且我也不想破坏 Web 服务器的注册表,即使我拥有所需的管理员权限。 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 (eg .NET).所有这些问题都是由于 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.因此,对我来说,解决方案的想法非常简单 - 使用多平台 NTLM 库在多平台技术中创建代理应用程序,其中 NTLM 通信是根据公共规范手动创建的,而不是通过在 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).我自己选择了 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);
    }
}

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.还请记住,某些 Web 服务要求您在初始请求中发送授权标头,而这不会这样做。 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.如果你需要这个,你需要创建你自己的 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 有效。

While writing a URL, put the '@' in front of url string. 在编写URL时,将“@”放在url字符串前面。

For example: 例如:

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

This will solve your problem. 这将解决您的问题。

The answer given by P.Brian.Mackey is also correct. P.Brian.Mackey给出的答案也是正确的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM