web-dev-qa-db-ja.com

HttpClientにリクエストと共に資格情報を渡す方法を教えてください。

Windowsサービスと通信するWebアプリケーション(IISでホストされている)があります。 WindowsサービスはASP.Net MVC Web API(自己ホスト型)を使用しているため、JSONを使用してhttp経由で通信できます。 Webアプリケーションは偽装を行うように構成されています。つまり、Webアプリケーションに要求を出すユーザーは、Webアプリケーションがサービスに要求を出すために使用するユーザーであるべきです。構造は次のようになります。

(赤で強調表示されているユーザーは、以下の例で参照されているユーザーです。)


Webアプリケーションは、 HttpClient を使用してWindowsサービスに要求を送信します。

var httpClient = new HttpClient(new HttpClientHandler() 
                      {
                          UseDefaultCredentials = true
                      });
httpClient.GetStringAsync("http://localhost/some/endpoint/");

これはWindowsサービスに要求をしますが、資格情報を正しく渡しません(サービスはユーザーをIIS APPPOOL\ASP.NET 4.0として報告します)。 これは私がやりたいことではありません

上記のコードを変更して代わりに WebClient を使用すると、ユーザーの資格情報は正しく渡されます。

WebClient c = new WebClient
                   {
                       UseDefaultCredentials = true
                   };
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));

上記のコードでは、サービスはユーザーをWebアプリケーションへの要求を行ったユーザーとして報告します。

HttpClientの実装で間違ったことをしていると、資格情報が正しく渡されません(またはHttpClientのバグです)。

HttpClientを使用したいのは、Tasksのasyc APIはイベントで処理する必要があるのに対して、WebClientsとうまく動作する非同期APIがあるからです。

133
adrianbanks

私もこれと同じ問題を抱えていました。次のSO記事で@tpeczekが行った調査のおかげで、同期ソリューションを開発しました。 HttpClientでASP.NET Web Apiサービスに対して認証できません

私のソリューションではWebClientを使用しています。これは、正しく指摘したように、資格情報を問題なく渡します。 HttpClientが機能しない理由は、Windowsセキュリティが偽装アカウントで新しいスレッドを作成する機能を無効にしているためです(上記のSOの記事を参照してください。)HttpClientはそのため、タスクファクトリがエラーを引き起こしています。一方、WebClientは、同じスレッドで同期的に実行されるため、ルールをバイパスし、その資格情報を転送します。

コードは機能しますが、欠点は非同期に機能しないことです。

var wi = (System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity;

var wic = wi.Impersonate();
try
{
    var data = JsonConvert.SerializeObject(new
    {
        Property1 = 1,
        Property2 = "blah"
    });

    using (var client = new WebClient { UseDefaultCredentials = true })
    {
        client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
        client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
    }
}
catch (Exception exc)
{
    // handle exception
}
finally
{
    wic.Undo();
}

注:NuGetパッケージ:Newtonsoft.Jsonが必要です。これは、WebAPIが使用するJSONシリアライザーと同じです。

58
Joshua

次のように自動的に資格情報を渡すようにHttpClientを設定できます。

myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
105
Sean

あなたがやろうとしているのは、NTLMに次のサーバーに識別情報を転送させることです。それはできません - それはあなたにローカルリソースへのアクセスのみを与える偽装をすることしかできません。それはあなたが機械の境界を越えることを許さないでしょう。 Kerberos認証はチケットを使用して委任(必要なもの)をサポートします。また、チェーン内のすべてのサーバーとアプリケーションが正しく構成され、ドメインにKerberosが正しくセットアップされていれば、チケットを転送できます。したがって、要するに、NTLMの使用からKerberosへの切り替えが必要です。

利用可能なWindows認証オプションとその機能の詳細については、次のサイトを参照してください。 http://msdn.Microsoft.com/ja-jp/library/ff647076.aspx

24
BlackSpy

それでは、上記のすべての貢献者に感謝します。私は.NET 4.6を使用していますが、私たちも同じ問題を抱えていました。私はSystem.Net.Http、具体的にはHttpClientHandlerのデバッグに時間をかけましたが、次のことがわかりました。

    if (ExecutionContext.IsFlowSuppressed())
    {
      IWebProxy webProxy = (IWebProxy) null;
      if (this.useProxy)
        webProxy = this.proxy ?? WebRequest.DefaultWebProxy;
      if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null)
        this.SafeCaptureIdenity(state);
    }

ExecutionContext.IsFlowSuppressed()が原因の可能性があると判断した後、私は偽装コードを次のようにラップしました。

using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate())
using (System.Threading.ExecutionContext.SuppressFlow())
{
    // HttpClient code goes here!
}

SafeCaptureIdenity内のコード(私のスペルミスではありません)は、偽装された身元であるWindowsIdentity.Current()をつかみます。これは流れを抑制しているためです。 use/disposeのため、これは呼び出し後にリセットされます。

それは今私たちのために働くようだ、ああ!

8
Chullybun

.NET Coreでは、System.Net.Http.HttpClientを使用して、認証済みのユーザーのWindows資格情報をバックエンドサービスに渡すためのUseDefaultCredentials = trueWindowsIdentity.RunImpersonatedで取得することができました。

HttpClient client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true } );
HttpResponseMessage response = null;

if (identity is WindowsIdentity windowsIdentity)
{
    await WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, async () =>
    {
        var request = new HttpRequestMessage(HttpMethod.Get, url)
        response = await client.SendAsync(request);
    });
}
5
scourge192

私はWindowsサービスでインターネットにアクセスできるユーザーをセットアップした後、それは私のために働きました。

私のコードでは:

HttpClientHandler handler = new HttpClientHandler();
handler.Proxy = System.Net.WebRequest.DefaultWebProxy;
handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
.....
HttpClient httpClient = new HttpClient(handler)
.... 

さて、私はJoshounコードを取り、それを一般的なものにしました。 SynchronousPostクラスにシングルトンパターンを実装する必要があるかどうかわかりません。もっと知識のある人が助けになるかもしれません。

実装

FileCategory x = new FileCategory { CategoryName = "Some Bs"};
SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>();
test.PostEntity(x, "/api/ApiFileCategories"); 

ここで総称クラス。どんな種類でも渡すことができます

 public class SynchronousPost<T>where T :class
    {
        public SynchronousPost()
        {
            Client = new WebClient { UseDefaultCredentials = true };
        }

        public void PostEntity(T PostThis,string ApiControllerName)//The ApiController name should be "/api/MyName/"
        {
            //this just determines the root url. 
            Client.BaseAddress = string.Format(
         (
            System.Web.HttpContext.Current.Request.Url.Port != 80) ? "{0}://{1}:{2}" : "{0}://{1}",
            System.Web.HttpContext.Current.Request.Url.Scheme,
            System.Web.HttpContext.Current.Request.Url.Host,
            System.Web.HttpContext.Current.Request.Url.Port
           );
            Client.Headers.Add(HttpRequestHeader.ContentType, "application/json;charset=utf-8");
            Client.UploadData(
                                 ApiControllerName, "Post", 
                                 Encoding.UTF8.GetBytes
                                 (
                                    JsonConvert.SerializeObject(PostThis)
                                 )
                             );  
        }
        private WebClient Client  { get; set; }
    }

興味があれば、私のApiクラスはこんな感じです

public class ApiFileCategoriesController : ApiBaseController
{
    public ApiFileCategoriesController(IMshIntranetUnitOfWork unitOfWork)
    {
        UnitOfWork = unitOfWork;
    }

    public IEnumerable<FileCategory> GetFiles()
    {
        return UnitOfWork.FileCategories.GetAll().OrderBy(x=>x.CategoryName);
    }
    public FileCategory GetFile(int id)
    {
        return UnitOfWork.FileCategories.GetById(id);
    }
    //Post api/ApileFileCategories

    public HttpResponseMessage Post(FileCategory fileCategory)
    {
        UnitOfWork.FileCategories.Add(fileCategory);
        UnitOfWork.Commit(); 
        return new HttpResponseMessage();
    }
}

私はninjectを使用しており、作業単位でレポ・パターンを作成しています。とにかく、上記のジェネリッククラスは本当に役に立ちます。

3
hidden