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
を使用したいのは、Task
sのasyc APIはイベントで処理する必要があるのに対して、WebClient
sとうまく動作する非同期APIがあるからです。
私もこれと同じ問題を抱えていました。次の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シリアライザーと同じです。
次のように自動的に資格情報を渡すようにHttpClient
を設定できます。
myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
あなたがやろうとしているのは、NTLMに次のサーバーに識別情報を転送させることです。それはできません - それはあなたにローカルリソースへのアクセスのみを与える偽装をすることしかできません。それはあなたが機械の境界を越えることを許さないでしょう。 Kerberos認証はチケットを使用して委任(必要なもの)をサポートします。また、チェーン内のすべてのサーバーとアプリケーションが正しく構成され、ドメインにKerberosが正しくセットアップされていれば、チケットを転送できます。したがって、要するに、NTLMの使用からKerberosへの切り替えが必要です。
利用可能なWindows認証オプションとその機能の詳細については、次のサイトを参照してください。 http://msdn.Microsoft.com/ja-jp/library/ff647076.aspx
それでは、上記のすべての貢献者に感謝します。私は.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のため、これは呼び出し後にリセットされます。
それは今私たちのために働くようだ、ああ!
.NET Coreでは、System.Net.Http.HttpClient
を使用して、認証済みのユーザーのWindows資格情報をバックエンドサービスに渡すためのUseDefaultCredentials = true
をWindowsIdentity.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);
});
}
私は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; }
}
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を使用しており、作業単位でレポ・パターンを作成しています。とにかく、上記のジェネリッククラスは本当に役に立ちます。