HttpClientにリクエストと一緒に認証情報を渡す方法は?


164

Windowsサービスと通信する(IISでホストされている)Webアプリケーションがあります。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でうまく機能する非同期APIがあるのに対して、WebClient's asyc APIはイベントで処理する必要があるからです。



HttpClientとWebClientは異なるものをDefaultCredentialsと見なしているようです。HttpClient.setCredentials(...)を試しましたか?
Germannアーリントン

ところで、WebClientはDownloadStringTaskAsync.Net 4.5にあり、非同期/待機でも使用できます
LB

1
@GermannArlington:メソッドHttpClientがありませんSetCredentials()。どういう意味か教えてもらえますか?
adrianbanks 2012

4
これは修正されたようです(.net 4.5.1)?new HttpClient(new HttpClientHandler() { AllowAutoRedirect = true, UseDefaultCredentials = true }Windows認証されたユーザーがアクセスするWebサーバーで作成を試みましたが、そのWebサイトはその後、別のリモートリソースに対して認証を行いました(フラグを設定しないと認証されません)。
GSerg、2015年

回答:


67

私も同じ問題を抱えていました。次の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が必要です。これは、Web APIが使用するJSONシリアライザーと同じです。


1
私は結局同じようなことをしました、そしてそれは本当にうまくいきます。呼び出しをブロックしたいので、非同期の問題は問題ではありません。
adrianbanks 2012年

136

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

var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });

11
私はそれを行う方法を知っています。動作は私が望むものではありません(質問で述べたように)-「これはWindowsサービスへの要求を行いますが、資格情報を正しく渡しません(サービスはユーザーをIIS APPPOOL \ ASP.NET 4.0として報告します)。私がしたいことではありません。」
adrianbanks 2013

4
これにより、iisでWindows認証のみが有効になっている問題が解決するようです。正当な資格情報を渡すだけでよい場合は、これで十分です。
Timmerz、2014年

これが偽装/委任シナリオのWebClientと同じように機能するかどうかはわかりません。上記のソリューションでHttpClientを使用すると「ターゲットプリンシパル名が正しくありません」と表示されますが、同様の設定でWebClientを使用すると、ユーザーの資格情報が渡されます。
Peder Rice

これは私にとってはうまくいき、ログは正しいユーザーを示しています。図のダブルホップでは、基になる認証スキームとしてNTLMで動作するとは思っていませんでしたが、動作します。
Nitin Rastogi 2017

最新バージョンのaspnetコアを使用して同じことをするには?(2.2)。誰かが知っている場合...
ニコ

26

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

利用可能なWindows認証オプションとその機能の詳細については、http//msdn.microsoft.com/en-us/library/ff647076.aspxを参照してください。


3
NTLMはIDを次のサーバーに転送しますが、それはできません」-使用時にこれを行うのはWebClientなぜですか?これは私が理解していないことです-それができない場合、どうしてそれやっているのですか?
adrianbanks 2012

2
Webクライアントを使用する場合、クライアントとサーバー間の接続は1つだけです。そのサーバー上のユーザー(1ホップ)を偽装することはできますが、それらの資格情報を別のマシンに転送することはできません(2ホップ-クライアントからサーバー、2番目のサーバー)。そのためには委任が必要です。
BlackSpy 2012

1
実行しようとしている方法で実行しようとしていることを達成する唯一の方法は、ユーザーにASP.NETアプリケーションのカスタムダイアログボックスにユーザー名とパスワードを入力させ、文字列として保存してから使用することですWeb APIプロジェクトに接続するときにIDを設定します。それ以外の場合は、NTLMを削除してKerberosに移動し、KerborosチケットをWeb APIプロジェクトに渡せるようにする必要があります。元の回答に添付したリンクを読むことを強くお勧めします。何をしようとしているのかは、始める前にWindows認証をよく理解している必要があります。
BlackSpy 2012

2
@BlackSpy:Windows認証の経験は豊富です。私が理解しようとしているのはWebClient、がNTLM資格情報を渡すHttpClientことができるが、できないことです。ASP.Net偽装のみを使用してこれを実現できます。Kerberosを使用したり、ユーザー名/パスワードを保存したりする必要はありません。ただし、これはでのみ機能しWebClientます。
adrianbanks 2012

1
ユーザー名とパスワードをテキストとして渡さずに、1ホップを超えて偽装することは不可能です。偽装の規則に違反し、NTLMはそれを許可しません。WebClientでは、資格情報を渡してボックスでそのユーザーとして実行するため、1ホップのジャンプが可能です。セキュリティログを見ると、ログインが表示されます。ユーザーはシステムにログインします。資格情報をテキストとして渡し、別のWebクライアントインスタンスを使用して次のボックスにログオンしない限り、そのマシンからそのユーザーとして実行することはできません。
BlackSpy 2012

17

さて、上記のすべての貢献者に感謝します。私は.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()ありません)は、なりすましのIDです。現在、フローを抑制しているため、これは取り上げられています。使用/破棄のため、これは呼び出し後にリセットされます。

それは今、私たちのために働くようです、ふphe!


2
この分析を行っていただきありがとうございます。これで私の状況も修正されました。これで、私のIDが他のWebアプリケーションに正しく渡されます。あなたは私に仕事の時間を節約しました!ティック数が多くないことに驚いています。
justdan23

必要なだけでusing (System.Threading.ExecutionContext.SuppressFlow())問題は解決しました!
ZX9

10

.NET Coreでは、を使用して、認証されたユーザーのWindows資格情報をバックエンドサービスに渡すためにSystem.Net.Http.HttpClientwith を取得することができました。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);
    });
}

4

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

私のコードでは:

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

3

わかりましたので、Joshounのコードを取り、それを汎用にしました。SynchronousPostクラスにシングルトンパターンを実装する必要があるかどうかはわかりません。多分より知識のある誰かが助けることができます。

実装

//私はあなたがあなた自身の具体的なタイプを持っていると思います。私の場合、最初にFileCategoryというクラスでコードを使用しています

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を使用しており、作業単位でレポパターンを使用しています。とにかく、上記のジェネリッククラスは本当に役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.