web-dev-qa-db-ja.com

各要求に対してC#WebClient NTLM認証が開始されます

単純なC#NET Framework 4.0アプリケーションを考えてみます。

  • webClientを使用
  • nTLMを使用して認証します(IIS 6.0およびIIS 7.5サーバーでテスト済み))
  • downloadString()を使用してURLから文字列を複数回取得します

正常に動作するサンプルは次のとおりです。

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認証が開始されます(サーバーは "WWW-Authenticate:NTLM"ヘッダーを返し、認証/承認プロセス全体が繰り返されます。 "Connection:close"ヘッダーはありません)。

NTLMは、要求ではなく接続を認証することになっているのではないですか?

各リクエストを再認証する必要をなくすために、WebClientに既存の接続を再利用させる方法はありますか?

15
c4n

10日間、考えられるすべてのことを試し、その過程で多くのことを学んだ後、私はついにこの問題の修正を見つけました。

コツは、UnsafeAuthenticatedConnectionSharingを上書きし、GetWebRequestでプロパティをtrueに設定することで、 HttpWebRequest を有効にすることです。

これを 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 に感謝します。彼の提案は私にとってはうまくいきませんでしたが、彼は私を正しい方向に向けて探し、最終的に答えを見つけました。

19
c4n

IISの設定を確認します。ただし、これがデフォルトであるはずです。

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

</windowsAuthentication>  

参照: https://msdn.Microsoft.com/en-us/library/aa347472.aspx

Localhost IISが存在するゾーンを確認しましたか?これは、WinInetを使用するときに過去にクライアント側からの落とし穴でもありました。WebClientのデフォルトの動作を確認してください。


編集:

エラーを再現した後、WebClientのNTLMpreauthentication実装が不足しているため、単一の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;
  }
}
2
Falco Alexander