基本認証については、ここにあるDarin Dimitrovの回答に示されている例に基づいて、カスタムHttpMessageHandler
を実装しました。 https://stackoverflow.com/a/11536349/270591
このコードは、ユーザー名とロールを持つタイプprincipal
のインスタンスGenericPrincipal
を作成し、このプリンシパルをスレッドの現在のプリンシパルに設定します。
Thread.CurrentPrincipal = principal;
後でApiController
メソッドで、コントローラーUser
プロパティにアクセスすることでプリンシパルを読み取ることができます。
public class ValuesController : ApiController
{
public void Post(TestModel model)
{
var user = User; // this should be the principal set in the handler
//...
}
}
最近MediaTypeFormatter
ライブラリを使用するカスタムTask
を追加するまで、これはうまくいくように見えました。
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream,
HttpContent content, IFormatterLogger formatterLogger)
{
var task = Task.Factory.StartNew(() =>
{
// some formatting happens and finally a TestModel is returned,
// simulated here by just an empty model
return (object)new TestModel();
});
return task;
}
(いくつかのサンプルコードからReadFromStreamAsync
のTask.Factory.StartNew
でタスクを開始するこのアプローチがあります。それは間違っているのでしょうか、それが問題の唯一の理由ですか?)
さて、「時々」-そして私にとってはランダムに見える-コントローラーメソッドのUser
プリンシパルは、MessageHandlerで設定したプリンシパル、つまりユーザー名、Authenticated
フラグではなくなりましたそして役割はすべて失われます。理由は、カスタムMediaTypeFormatterがMessageHandlerとコントローラーメソッドの間でスレッドの変更を引き起こすためと思われます。 MessageHandlerとコントローラーメソッドでThread.CurrentThread.ManagedThreadId
の値を比較することでこれを確認しました。 「時々」それらは異なり、そしてプリンシパルは「失われます」。
Thread.CurrentPrincipal
を設定して、カスタムMessageHandlerからコントローラーメソッドにプリンシパルを安全に転送し、 このブログ投稿 リクエストプロパティを使用する代わりの方法を探しました。
request.Properties.Add(HttpPropertyKeys.UserPrincipalKey,
new GenericPrincipal(identity, new string[0]));
私はそれをテストしたかったのですが、HttpPropertyKeys
クラス(名前空間System.Web.Http.Hosting
にある)には、最近のWebApiバージョン(リリース候補と最終リリース)でUserPrincipalKey
プロパティがもうないようです先週からも)。
私の質問は、上記の最後のコードスニペットを現在のWebAPIバージョンで動作するように変更するにはどうすればよいですか?または一般的に:カスタムMessageHandlerでユーザープリンシパルを設定し、コントローラーメソッドで確実にアクセスするにはどうすればよいですか?
編集
here に言及されている「HttpPropertyKeys.UserPrincipalKey
...は“MS_UserPrincipal”
」に解決されるので、私は使用しようとしました:
request.Properties.Add("MS_UserPrincipal",
new GenericPrincipal(identity, new string[0]));
しかし、期待どおりに機能しません。ApiController.User
プロパティには、上記のProperties
コレクションに追加されたプリンシパルが含まれていません。
新しいスレッドでプリンシパルを失う問題は、次のとおりです。
http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/
重要:ASP.NET Web APIでクライアントプリンシパルを設定する
ASP.NETに深く埋め込まれたいくつかの不幸なメカニズムのため、Web API WebホスティングでThread.CurrentPrincipalを設定するだけでは十分ではありません。
ASP.NETでホストしている場合、新しいスレッドの作成時にThread.CurrentPrincipalがHttpContext.Current.Userでオーバーライドされる場合があります。つまり、スレッドとHTTPコンテキストの両方でプリンシパルを設定する必要があります。
そしてここ: http://aspnetwebstack.codeplex.com/workitem/264
今日、カスタムメッセージハンドラーを使用してWebホストシナリオで認証を実行する場合、ユーザープリンシパルに次の両方を設定する必要があります。
IPrincipal principal = new GenericPrincipal( new GenericIdentity("myuser"), new string[] { "myrole" }); Thread.CurrentPrincipal = principal; HttpContext.Current.User = principal;
最後の行を追加しましたHttpContext.Current.User = principal
(必要なusing System.Web;
)メッセージハンドラへ、およびUser
内のApiController
プロパティは、MediaTypeFormatterのタスクによりスレッドが変更された場合でも、常に正しいプリンシパルを持つようになりました。
編集
強調するために、現在のユーザーのHttpContext
のプリンシパルの設定は、WebApiがASP.NET/IISでホストされている場合にのみ必要です。セルフホスティングの場合、それは必要ありません(HttpContext
はASP.NET構造であり、セルフホスティングでは存在しないため不可能です)。
カスタムMessageHandlerを使用して、MS_UserPrincipal
で定義されているHttpRequestMessageExtensionMethods.SetUserPrincipal
拡張メソッドを呼び出すことにより、System.ServiceModel.Channels
プロパティを追加できます。
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var user = new GenericPrincipal(new GenericIdentity("UserID"), null);
request.SetUserPrincipal(user);
return base.SendAsync(request, cancellationToken);
}
これは、このプロパティをリクエストのプロパティコレクションに追加するだけであり、ApiControllerにアタッチされたユーザーは変更しないことに注意してください。
コンテキストの切り替えを回避するには、TaskCompletionSource<object>
カスタムMediaTypeFormatter
で別のタスクを手動で開始する代わりに:
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var tcs = new TaskCompletionSource<object>();
// some formatting happens and finally a TestModel is returned,
// simulated here by just an empty model
var testModel = new TestModel();
tcs.SetResult(testModel);
return tcs.Task;
}