2012-08-19 5 views
47

Per l'autenticazione di base che ho implementato un personalizzato HttpMessageHandler sull'esempio mostrato nella risposta di Darin Dimitrov qui: https://stackoverflow.com/a/11536349/270591Come posso impostare in modo sicuro l'utente principale in un WebAPI personalizzato HttpMessageHandler?

Il codice crea un'istanza principal di tipo GenericPrincipal con nome utente e ruoli e quindi imposta questo principio alla principale corrente del filo:

Thread.CurrentPrincipal = principal; 

seguito in un metodo ApiController il principale può essere letta mediante l'accesso alla controllori User proprietà:

public class ValuesController : ApiController 
{ 
    public void Post(TestModel model) 
    { 
     var user = User; // this should be the principal set in the handler 
     //... 
    } 
} 

Questo sembrava funzionare benissimo fino a che recentemente aggiunto un personalizzato MediaTypeFormatter che utilizza la libreria Task in questo modo:

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; 
} 

(ho questo approccio per iniziare un'attività con Task.Factory.StartNew in ReadFromStreamAsync da alcuni esempi di codice. È sbagliato e forse l'unica ragione del problema?)

Ora, "a volte" - e per me sembra essere casuale - il principal User nel metodo controller non è più il principal che ho impostato in MessageHandler, ovvero nome utente, flag e tutti i ruoli vengono persi. Il motivo sembra essere che il MediaTypeFormatter personalizzato provoca una modifica del thread tra MessageHandler e il metodo controller. L'ho confermato confrontando i valori di Thread.CurrentThread.ManagedThreadId nel MessageHandler e nel metodo del controller. "A volte" sono diversi e quindi il principale è "perso".

Ho guardato ora un'alternativa all'impostazione Thread.CurrentPrincipal per trasferire in qualche modo il capitale in modo sicuro dal messageHandler personalizzato al metodo di controllo e in this blog post proprietà delle richieste sono utilizzati:

request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, 
    new GenericPrincipal(identity, new string[0])); 

ho voluto mettere alla prova questo, ma sembra che la classe HttpPropertyKeys (che si trova nello spazio dei nomi System.Web.Http.Hosting) non abbia più una proprietà UserPrincipalKey nelle versioni WebApi recenti (release candidate e versione finale della scorsa settimana).

La mia domanda è: come posso modificare l'ultimo frammento di codice sopra in modo che funzioni con la versione WebAPI corrente? O in generale: come posso impostare l'utente principale in un MessageHandler personalizzato e accedervi in ​​modo affidabile in un metodo di controllo?

Modifica

È menzionato here che "HttpPropertyKeys.UserPrincipalKey ... decide di “MS_UserPrincipal”", così ho cercato di usare:

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

Ma non funziona come mi aspettavo: la La proprietà ApiController.User non contiene il principal aggiunto alla raccolta Properties sopra.

+0

Suggerisco di guardare questo https://github.com/thinktecture/Thinktecture.IdentityModel.45 –

risposta

72

Il problema di perdere il capitale di un nuovo thread è accennato qui:

http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/

Importante: impostazione del client principale nell'API Web ASP.NET

A causa di alcuni sfortunati meccanismi sepolti in profondità in ASP.NET, l'impostazione di Thread.CurrentPrincipal nell'hosting Web Web API non è sufficiente.

Quando si esegue l'hosting in ASP.NET, Thread.CurrentPrincipal potrebbe essere sostituito da con HttpContext.Current.User durante la creazione di nuovi thread. Questo significa che devi impostare l'entità sia sul thread che sul contesto HTTP.

E qui: http://aspnetwebstack.codeplex.com/workitem/264

Oggi, è necessario impostare entrambe le seguenti operazioni per l'utente principale se si utilizza un gestore di messaggi personalizzato per eseguire l'autenticazione nello scenario web ospitato.

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

ho aggiunto l'ultima riga HttpContext.Current.User = principal (ha bisogno using System.Web;) al gestore di messaggi e la proprietà User nel ApiController ha sempre il principio corretto ora, anche se il thread è cambiato a causa del compito in il MediaTypeFormatter.

Modifica

solo sottolineare che: Impostare il principale dell'utente corrente del HttpContext è necessaria solo quando il WebAPI è ospitato in ASP.NET/IIS. Per l'auto-hosting non è necessario (e non è possibile perché HttpContext è un costrutto ASP.NET e non esiste quando è ospitato autonomamente).

5

Per evitare il cambio di contesto provare a utilizzare un TaskCompletionSource<object> invece di partire manualmente un altro compito nella vostra abitudine 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

Funziona, ma sono un po 'preoccupato di dover essere così attento con l'uso di attività e thread per mantenere un caratteristica di sicurezza intatta. Ho trovato un'altra soluzione, vedere la mia risposta qui. – Slauma

+0

@Darin Dimitrov, ho installato l'RTM di vs 2012 e gli ultimi bit di asp.net web api e HttpPropertyKeys.UserPrincipalKey fornisce l'errore di sintassi. la classe di visualizzazione nel browser degli oggetti mostra che questa chiave non è presente nella versione finale. Qualche idea cos'altro posso usare? –

+1

Perché si desidera utilizzare 'HttpPropertyKeys.UserPrincipalKey'?Dove nella mia risposta hai visto usarlo? Quindi non usarlo. –

4

Utilizzando la vostra abitudine messageHandler si potrebbe aggiungere la proprietà MS_UserPrincipal chiamando il metodo HttpRequestMessageExtensionMethods.SetUserPrincipal estensione definito in 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); 
} 

Si noti che questo non fa che aumentare questa proprietà per la raccolta delle proprietà della richiesta, non cambia il Utente collegato all'ApiController.

Problemi correlati