2012-08-19 23 views
47

Para la autenticación básica que han puesto en marcha una costumbre HttpMessageHandler basado en el ejemplo mostrado en la respuesta de Darin Dimitrov aquí: https://stackoverflow.com/a/11536349/270591¿Cómo puedo establecer de forma segura el principal del usuario en un WebAPI HttpMessageHandler personalizado?

El código crea una instancia principal de tipo GenericPrincipal con el nombre y los roles de usuario y luego establece este principio al director actual de la rosca:

Thread.CurrentPrincipal = principal; 

Más tarde, en un método ApiController el principal puede ser leído mediante el acceso a los controladores User propiedad:

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 he añadido recientemente un encargo MediaTypeFormatter que utiliza la biblioteca Task así:

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 de un código de ejemplo. ¿Es incorrecto y tal vez la única razón para el problema?)

Ahora, "a veces" - y para mí parece ser aleatorio - el principal User en el método del controlador ya no es el principal que he establecido el MessageHandler, es decir, el nombre de usuario, el indicador Authenticated y todos 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 mirado ahora para alternativa a la configuración Thread.CurrentPrincipal para transferir de alguna manera el director de seguridad de la MessageHandler personalizado al método de controlador y en this blog post propiedades de la solicitud se utilizan:

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

Se menciona here que "... HttpPropertyKeys.UserPrincipalKey resuelve a “MS_UserPrincipal”", por lo que trató de usar:

request.Properties.Add("MS_UserPrincipal", 
    new GenericPrincipal(identity, new string[0])); 

Pero no funciona como esperaba: La ApiController.User propiedad no contiene el principal agregado a la colección Properties anterior.

+0

Sugiero mirar este https://github.com/thinktecture/Thinktecture.IdentityModel.45 –

Respuesta

72

El problema de la pérdida del 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: configuración del cliente principal en ASP.NET Web API

Debido a algunos mecanismos desafortunados enterrados profundamente en ASP.NET, configuración Thread.CurrentPrincipal en Web API de alojamiento web no es suficiente.

Al hospedar en ASP.NET, Thread.CurrentPrincipal puede sobrescribirse con HttpContext.Current.User al crear nuevos subprocesos. Esto significa tiene que establecer el principal tanto en el hilo como en el contexto HTTP.

Y aquí: http://aspnetwebstack.codeplex.com/workitem/264

Hoy en día, tendrá que configurar los dos siguientes para principal de usuario si utiliza un controlador de mensaje personalizado para realizar la autenticación en el escenario alojado web.

IPrincipal principal = new GenericPrincipal(
    new GenericIdentity("myuser"), new string[] { "myrole" }); 
Thread.CurrentPrincipal = principal; 
HttpContext.Current.User = principal; 

he añadido la última línea HttpContext.Current.User = principal (necesita using System.Web;) para el controlador de mensajes y la propiedad User en el ApiController siempre tiene principal correcto, ahora, incluso si el mensaje ha cambiado debido a la tarea de MediaTypeFormatter.

Editar

Sólo para enfatizar que: Configuración principal del usuario actual del HttpContext sólo es necesaria cuando el WebAPI está alojado en ASP.NET/IIS. Para autohospedarse no es necesario (y no es posible porque HttpContext es una construcción ASP.NET y no existe cuando se aloja automáticamente).

5

Para evitar el cambio de contexto trate de usar un TaskCompletionSource<object> en lugar de comenzar manualmente otra tarea en su aduana 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; 
} 
+2

Funciona, pero estoy un poco preocupado de tener que ser tan cuidadoso con el uso de tareas y subprocesos para mantener un característica de seguridad intacta. He encontrado otra solución, mira mi propia respuesta aquí. – Slauma

+0

@Darin Dimitrov, he instalado el RTM de vs 2012 y los últimos bits de asp.net web api y HttpPropertyKeys.UserPrincipalKey da error de sintaxis. ver la clase en el navegador de objetos muestra que esta clave no está presente en la versión final. ¿Alguna idea de qué más puedo usar? –

+1

¿Por qué quieres usar 'HttpPropertyKeys.UserPrincipalKey'?¿En qué parte de mi respuesta viste usarlo? Entonces no lo uses. –

4

Usando su costumbre MessageHandler se podría añadir la propiedad MS_UserPrincipal llamando al método HttpRequestMessageExtensionMethods.SetUserPrincipal extensión definida 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 sólo se suma esta propiedad a la colección de propiedades de la solicitud, que no cambia el Usuario adjunto al ApiController.

Cuestiones relacionadas