C#WebClient NTLM身份验证针对每个请求开始

C#WebClient NTLM身份验证针对每个请求开始

问题描述:

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

Consider a simple C# NET Framework 4.0 application, that:

  • 使用WebClient
  • 使用NTLM进行身份验证(在IIS 6.0和IIS 7.5服务器上测试)
  • 使用DownloadString()从URL多次检索字符串

这是一个效果很好的示例:

Here's a sample that works fine:

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身份验证未持久.

When enabling tracing I see that the NTLM authentication does not persist.

每次调用Webclient.DownloadString时,NTLM身份验证就会开始(服务器返回"WWW-Authenticate:NTLM"标头,并且整个身份验证/授权过程重复;没有"Connection:close"标头).

Each time Webclient.DownloadString is called, NTLM authentication starts (server returns "WWW-Authenticate: NTLM" header and the whole authenticate/authorize process repeats; there is no "Connection: close" header).

NTLM是否不应该对连接进行身份验证,而不是对请求进行身份验证?

Wasn't NTLM supposed to authenticate a connection, not a request?

是否有一种方法可以使WebClient重用现有的连接,而不必重新验证每个请求?

Is there a way to make WebClient reuse an existing connection to avoid having to re-authenticate each request?

经过10天的尝试,并在此过程中学到了很多东西,我最终找到了解决此问题的方法

After 10 days of trying everything I could think of and learning a lot in the process, I finally figured a fix for this issue.

诀窍是启用

The trick is to enable UnsafeAuthenticatedConnectionSharing by overriding GetWebRequest and setting the property to true in the HttpWebRequest you get back.

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

You may want to combine that with the ConnectionGroupName property to avoid the potential use of the connection by unauthenticated applications.

这里是问题经修改后可以正常工作的示例.它打开单个NTLM身份验证的连接并重新使用它:

Here is the sample from the question modified to work as expected. It opens a single NTLM authenticated connection and reuses it:

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 所提供的所有帮助;虽然他的建议对我不太有效,但他的确为我指出了正确的方向,以寻找并最终找到答案.

At this point I would also like to thank @Falco Alexander for all the help; while his suggestions didn't quite work for me, he did point me in the right direction to look for and finally find the answer.