Подтвердить что ты не робот

Как я могу безопасно установить пользовательский принцип в пользовательском веб-приложении 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 выше.

4b9b3361

Ответ 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.