Как я могу безопасно установить пользовательский принцип в пользовательском веб-приложении HttpMessageHandler?
Для базовой проверки подлинности я реализовал пользовательский HttpMessageHandler
на примере, показанном в Дарине Димитров, здесь: qaru.site/info/114619/...
Код создает экземпляр 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;
}
(У меня есть такой подход, чтобы запустить задачу с Task.Factory.StartNew
в ReadFromStreamAsync
из некоторого примера кода. Это неправильно и, возможно, единственная причина проблемы?)
Теперь "иногда" - и для меня это кажется случайным - принцип User
в методе контроллера больше не является основным, который я установил в MessageHandler, то есть имя пользователя, Authenticated
и роли потеряны. Причина в том, что пользовательский MediaTypeFormatter вызывает изменение потока между MessageHandler и методом контроллера. Я подтвердил это, сравнив значения Thread.CurrentThread.ManagedThreadId
в MessageHandler и в методе контроллера. "Иногда" они разные, а затем "потеряно".
Теперь я искал альтернативу настройке Thread.CurrentPrincipal
, чтобы как-то передать принципала безопасно из пользовательского MessageHandler в метод контроллера и в этот блог post:
request.Properties.Add(HttpPropertyKeys.UserPrincipalKey,
new GenericPrincipal(identity, new string[0]));
Я хотел проверить это, но кажется, что класс HttpPropertyKeys
(который находится в пространстве имен System.Web.Http.Hosting
) больше не имеет свойства UserPrincipalKey
в последних версиях WebApi (релиз-кандидат и окончательный выпуск с прошлой недели также).
Мой вопрос: как я могу изменить последний фрагмент кода выше, чтобы он работал с текущей версией WebAPI? Или вообще: как я могу установить пользовательский принцип в пользовательском MessageHandler и надежно получить его в методе контроллера?
Edit
Здесь упоминается , что "HttpPropertyKeys.UserPrincipalKey
... разрешает "MS_UserPrincipal"
", поэтому я попытался использовать:
request.Properties.Add("MS_UserPrincipal",
new GenericPrincipal(identity, new string[0]));
Но он не работает так, как я ожидал: свойство ApiController.User
не содержит принципала, добавленного в коллекцию Properties
выше.
Ответы
Ответ 1
Здесь упоминается проблема потери принципала в новом потоке:
http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/
Важно: настройка принципала клиента в веб-интерфейсе ASP.NET
Из-за некоторых неудачных механизмов, глубоко погруженных в ASP.NET, установка Thread.CurrentPrincipal в веб-хостинге веб-API недостаточно.
При размещении в ASP.NET Thread.CurrentPrincipal может быть переопределен с HttpContext.Current.User при создании новых потоков. Это означает вы должны установить принципала как в потоке, так и в контексте HTTP.
И здесь: http://aspnetwebstack.codeplex.com/workitem/264
Сегодня для пользователя будет необходимо установить оба из следующих правил: если вы используете специальный обработчик сообщений для выполнения проверки подлинности в веб-хостинга.
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.
Edit
Просто чтобы подчеркнуть это: установка текущего пользовательского принципала HttpContext
необходима, только если WebApi размещен в ASP.NET/IIS. Для самостоятельного хостинга это необязательно (и не возможно, потому что HttpContext
является конструкцией ASP.NET и не существует при самообслуживании).
Ответ 2
Чтобы избежать переключения контекста, попробуйте использовать 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;
}
Ответ 3
Используя свой собственный 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.