asp.net - studio - web api c# tutorial
¿Cómo puedo configurar de manera segura el principal del usuario en un WebAPI HttpMessageHandler personalizado? (3)
Con su MessageHandler personalizado, puede agregar la propiedad MS_UserPrincipal
llamando al método de extensión HttpRequestMessageExtensionMethods.SetUserPrincipal
definido en 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);
}
Tenga en cuenta que esto solo agrega esta propiedad a la colección Propiedades de la Solicitud, no cambia el Usuario adjunto al ApiController.
Para la autenticación básica, he implementado un HttpMessageHandler
personalizado basado en el ejemplo que se muestra en la respuesta de Darin Dimitrov aquí: https://stackoverflow.com/a/11536349/270591
El código crea un principal
de instancia de tipo GenericPrincipal
con nombre de usuario y roles y luego establece este principal para el principal actual del hilo:
Thread.CurrentPrincipal = principal;
Más adelante, en un método ApiController
, el principal se puede leer accediendo a los controladores User
Propiedad del User
:
public class ValuesController : ApiController
{
public void Post(TestModel model)
{
var user = User; // this should be the principal set in the handler
//...
}
}
Esto pareció funcionar bien hasta que recientemente agregué un MediaTypeFormatter
personalizado que usa la biblioteca de Task
manera:
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;
}
(Tengo este enfoque para comenzar una tarea con Task.Factory.StartNew
en ReadFromStreamAsync
partir de un código de ejemplo. ¿Es incorrecto y tal vez la única razón del problema?)
Ahora, "algunas veces", y para mí parece ser aleatorio, el principal del User
en el método del controlador ya no es el principal que he configurado en MessageHandler, es decir, el nombre de usuario, el indicador Authenticated
y los roles se han perdido. La razón parece ser que el MediaTypeFormatter personalizado provoca un cambio del hilo entre MessageHandler y el método del controlador. Lo he confirmado comparando los valores de Thread.CurrentThread.ManagedThreadId
en MessageHandler y en el método del controlador. "A veces" son diferentes y luego el director está "perdido".
He buscado ahora una alternativa a la configuración de Thread.CurrentPrincipal
para transferir de alguna manera el principal de forma segura desde el MessageHandler personalizado al método del controlador y en este blog se solicitan las propiedades de solicitud:
request.Properties.Add(HttpPropertyKeys.UserPrincipalKey,
new GenericPrincipal(identity, new string[0]));
Quería probar eso, pero parece que la clase HttpPropertyKeys
(que está en el espacio de nombres System.Web.Http.Hosting
) ya no tiene una propiedad UserPrincipalKey
en las versiones recientes de WebApi (versión de lanzamiento y versión final de la semana pasada también) .
Mi pregunta es: ¿cómo puedo cambiar el último fragmento de código anterior para que funcione con la versión actual de WebAPI? O en general: ¿cómo puedo configurar el principal del usuario en un MessageHandler personalizado y acceder de manera confiable en un método de controlador?
Editar
here se menciona que " HttpPropertyKeys.UserPrincipalKey
... se resuelve en “MS_UserPrincipal”
", así que traté de usar:
request.Properties.Add("MS_UserPrincipal",
new GenericPrincipal(identity, new string[0]));
Pero no funciona como esperaba: la propiedad ApiController.User
no contiene el principal agregado a la colección de Properties
anterior.
El problema de perder el principal en un nuevo hilo se menciona aquí:
http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/
Importante: configurar el cliente principal en ASP.NET Web API
Debido a algunos mecanismos desafortunados enterrados profundamente en ASP.NET, configurar Thread.CurrentPrincipal en Web API web hosting no es suficiente.
Cuando se aloja en ASP.NET, Thread.CurrentPrincipal puede sobrescribirse con HttpContext.Current.User al crear nuevos hilos. Esto significa que debe establecer el principal tanto en el hilo como en el contexto HTTP.
Y aquí: http://aspnetwebstack.codeplex.com/workitem/264
Hoy, necesitará establecer los dos siguientes para el principal del usuario si usa un manejador de mensajes personalizado para realizar la autenticación en el escenario alojado en la web.
IPrincipal principal = new GenericPrincipal( new GenericIdentity("myuser"), new string[] { "myrole" }); Thread.CurrentPrincipal = principal; HttpContext.Current.User = principal;
He agregado la última línea HttpContext.Current.User = principal
(necesita using System.Web;
) al manejador de mensajes y la propiedad User
en el ApiController
siempre tiene el principal correcto ahora, incluso si el hilo ha cambiado debido a la tarea en MediaTypeFormatter.
Editar
Solo para enfatizarlo: establecer el principal del usuario actual de HttpContext
solo es necesario cuando WebApi está alojado en ASP.NET/IIS. Para autohospedarse no es necesario (y no es posible porque HttpContext
es una construcción de ASP.NET y no existe cuando se aloja automáticamente).
Para evitar el cambio de contexto, intente utilizar TaskCompletionSource<object>
lugar de iniciar manualmente otra tarea en su MediaTypeFormatter
personalizado:
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;
}