为每个请求启动 C# WebClient NTLM 身份验证

C# WebClient NTLM authentication starting for each request

考虑一个简单的 C# NET Framework 4.0 应用程序,即:

这是一个运行良好的示例:

using System;
using System.Net;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string URL_status = "http://localhost/status";

            CredentialCache myCache = new CredentialCache();
            myCache.Add(new Uri(URL_status), "NTLM", new NetworkCredential("username", "password", "domain"));

            WebClient WebClient = new WebClient();
            WebClient.Credentials = myCache;

            for (int i = 1; i <= 5; i++)
            {
                string Result = WebClient.DownloadString(new Uri(URL_status));
                Console.WriteLine("Try " + i.ToString() + ": " + Result);
            }

            Console.Write("Done");
            Console.ReadKey();
        }
    }
}

问题:

启用跟踪时,我发现 NTLM 身份验证没有持续存在。

每次调用 Webclient.DownloadString 时,NTLM 身份验证开始(服务器 returns "WWW-Authenticate: NTLM" header 并且整个 authenticate/authorize 过程重复;没有 "Connection: close" header).

NTLM 不是应该验证连接,而不是请求吗?

有没有办法让 WebClient 重用现有连接以避免必须 re-authenticate 每个请求?

检查您的 IIS 设置,虽然这应该是默认设置。

<windowsAuthentication
   enabled="True"
   authPersistSingleRequest="False"
   UseKernelMode>

</windowsAuthentication>  

参考:https://msdn.microsoft.com/en-us/library/aa347472.aspx

您是否检查过您的本地主机 IIS 所在的区域?这也是以往客户端在使用 WinInet 时的一个陷阱。检查 WebClient.

的默认行为

编辑:

重现错误后,我发现是 WebClient 缺少的 NTLM preauthentication 实现使您无法收到单个 401 请求:

var WebClient = new PreAuthWebClient();
WebClient.Credentials = new NetworkCredential("user", "pass","domain");

//Do your GETs 

Public class PreAuthWebClient: WebClient
{
    protected override WebRequest GetWebRequest (Uri address)
    {
        WebRequest request = (WebRequest) base.GetWebRequest (address);
        request.PreAuthenticate = true;
        return request;
  }
}

在尝试了 10 天我能想到的一切并在此过程中学到了很多东西之后,我终于找到了解决此问题的方法。

诀窍是通过覆盖 GetWebRequest 并在 HttpWebRequest 中将 属性 设置为 true 来启用 UnsafeAuthenticatedConnectionSharing

您可能希望将其与 ConnectionGroupName 属性 结合使用,以避免未经身份验证的应用程序可能使用该连接。

这里是问题中的示例,已修改为按预期工作。它打开单个 NTLM 身份验证连接并重新使用它:

using System;
using System.Net;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string URL_status = "http://localhost/status";

            CredentialCache myCache = new CredentialCache();
            myCache.Add(new Uri(URL_status), "NTLM", new NetworkCredential("username", "password", "domain"));

            MyWebClient webClient = new MyWebClient();
            webClient.Credentials = myCache;

            for (int i = 1; i <= 5; i++)
            {
                string result = webClient.DownloadString(new Uri(URL_status));
                Console.WriteLine("Try {0}: {1}", i, result);
            }

            Console.Write("Done");
            Console.ReadKey();
        }
    }

    public class MyWebClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {
            WebRequest request = base.GetWebRequest(address);

            if (request is HttpWebRequest) 
            {
                var myWebRequest = request as HttpWebRequest;
                myWebRequest.UnsafeAuthenticatedConnectionSharing = true;
                myWebRequest.KeepAlive = true;
            }

            return request;
        }
    }   
}

此时我还要感谢@Falco Alexander的所有帮助;虽然他的建议对我来说不太奏效,但他确实为我指明了寻找并最终找到答案的正确方向。