2010-05-15 15 views
64

Ho un'applicazione che utilizza l'autenticazione basata su form ASP.NET. Per la maggior parte, funziona alla grande, ma sto cercando di aggiungere il supporto per una semplice API tramite un file .ashx. Voglio che il file ashx abbia un'autenticazione opzionale (ad esempio se non si fornisce un'intestazione di autenticazione, funziona semplicemente in modo anonimo). Ma, a seconda di quello che fai, voglio richiedere l'autenticazione in determinate condizioni.Autenticazione moduli: disabilitare il reindirizzamento alla pagina di login

Ho pensato che sarebbe stato semplice rispondere con il codice di stato 401 se non è stata fornita l'autenticazione richiesta, ma sembra che il modulo Forms Authentcation lo stia intercettando e rispondendo con un reindirizzamento alla pagina di accesso. Quello che voglio dire è che, se il mio metodo ProcessRequest assomiglia a questo:

public void ProcessRequest(HttpContext context) 
{ 
    Response.StatusCode = 401; 
    Response.StatusDescription = "Authentication required"; 
} 

Poi invece di ottenere un codice di errore 401 sul client, come mi aspetto, io sono in realtà ottenere un 302 reindirizzamento alla pagina di login .

Per il traffico HTTP nono, posso vedere come sarebbe utile, ma per la mia pagina API, voglio che il 401 passi senza modifiche in modo che il chiamante sul lato client possa rispondere ad esso al posto di programmazione.

C'è un modo per farlo?

risposta

63

ASP.NET 4.5 ha aggiunto la proprietà booleana HttpResponse.SuppressFormsAuthenticationRedirect.

public void ProcessRequest(HttpContext context) 
{ 
    Response.StatusCode = 401; 
    Response.StatusDescription = "Authentication required"; 
    Response.SuppressFormsAuthenticationRedirect = true; 
} 
+0

Questa è la risposta per chiunque usi .NET 4.5. Peccato che non l'abbia letto fin qui nella lista e ho dovuto trovarlo da solo nelle fonti decompilate! –

+2

È possibile inserire questo evento Global.asax 'Application_EndRequest' per applicarlo sull'intera applicazione. [Per esempio.] (Http://stackoverflow.com/a/18519725/188246) –

+3

Ho usato uno snippet simile nel metodo Application_BeginRequest con grande successo. La richiesta è AJAX? Imposta la bandiera immediatamente. –

-2

Cerca all'interno del file Web.config in configuration\authentication. Se esiste un sottoelemento forms con un attributo , rimuoverlo e riprovare.

+2

Rimozione che ha appena modificato l'URL reindirizzato a '/ login.aspx' al posto di quello personalizzato. Voglio comunque mantenere l'autenticazione basata su form per le pagine "normali", solo per questo file .ashx volevo cambiarlo. –

33

Dopo un po 'di indagini, sembra che lo FormsAuthenticationModule aggiunga un gestore per l'evento HttpApplicationContext.EndRequest. Nel suo gestore, controlla per un codice di stato 401 e fondamentalmente fa un Response.Redirect(loginUrl) invece. Per quanto posso dire, non c'è modo di ignorare questo comportamento se si desidera utilizzare FormsAuthenticationModule.

Il modo in cui ho finito per ottenere intorno era disabilitando il FormsAuthenticationModule nel web.config in questo modo:

<authentication mode="None" /> 

E poi l'attuazione del Application_AuthenticateEvent me stesso:

void Application_AuthenticateRequest(object sender, EventArgs e) 
{ 
    if (Context.User == null) 
    { 
     var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName); 
     if (oldTicket != null && !oldTicket.Expired) 
     { 
      var ticket = oldTicket; 
      if (FormsAuthentication.SlidingExpiration) 
      { 
       ticket = FormsAuthentication.RenewTicketIfOld(oldTicket); 
       if (ticket == null) 
        return; 
      } 

      Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]); 
      if (ticket != oldTicket) 
      { 
       // update the cookie since we've refreshed the ticket 
       string cookieValue = FormsAuthentication.Encrypt(ticket); 
       var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ?? 
          new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath }; 

       if (ticket.IsPersistent) 
        cookie.Expires = ticket.Expiration; 
       cookie.Value = cookieValue; 
       cookie.Secure = FormsAuthentication.RequireSSL; 
       cookie.HttpOnly = true; 
       if (FormsAuthentication.CookieDomain != null) 
        cookie.Domain = FormsAuthentication.CookieDomain; 
       Context.Response.Cookies.Remove(cookie.Name); 
       Context.Response.Cookies.Add(cookie); 
      } 
     } 
    } 
} 

private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name) 
{ 
    FormsAuthenticationTicket ticket = null; 
    string encryptedTicket = null; 

    var cookie = context.Request.Cookies[name]; 
    if (cookie != null) 
    { 
     encryptedTicket = cookie.Value; 
    } 

    if (!string.IsNullOrEmpty(encryptedTicket)) 
    { 
     try 
     { 
      ticket = FormsAuthentication.Decrypt(encryptedTicket); 
     } 
     catch 
     { 
      context.Request.Cookies.Remove(name); 
     } 

     if (ticket != null && !ticket.Expired) 
     { 
      return ticket; 
     } 

     // if the ticket is expired then remove it 
     context.Request.Cookies.Remove(name); 
     return null; 
    } 
} 

In realtà è un po 'più complicato di così, ma fondamentalmente ho ottenuto il codice osservando l'implementazione di FormsAuthenticationModule nel riflettore. La mia implementazione è diversa dal FormsAuthenticationModule integrato in quanto non fa nulla se rispondi con un 401 - nessun reindirizzamento alla pagina di accesso. Immagino che se mai diventasse un requisito, potrei inserire un elemento nel contesto per disabilitare l'auto-reindirizzamento o qualcosa del genere.

+1

Funziona anche molto bene in ASP.NET MVC. –

+2

Nota: la [risposta di zacharydl] (http://stackoverflow.com/a/16846041/241462) sopra è la strada da percorrere in ASP.NET 4.5+ –

11

Non sono sicuro se questo funzionerà per tutti, ma in IIS7 è possibile chiamare Response.End() dopo aver impostato il codice di stato e la descrizione. In questo modo, che # & $^# @ *! FormsAuthenticationModule non eseguirà un reindirizzamento.

public void ProcessRequest(HttpContext context) { 
    Response.StatusCode = 401; 
    Response.StatusDescription = "Authentication required"; 
    Response.End(); 
} 
+3

Bello uno John! Non è ridicolo quanto dobbiamo lavorare duramente per aggirare questo?HttpContext ha una proprietà SkipAuthorization, sarebbe bello se avesse SkipLoginRedirect. O se l'autenticazione dei moduli potrebbe essere limitata e sovrascritta dalle directory. –

+0

Quando stavo provando, ero su IIS 6 e penso che funzioni un po 'diversamente per IIS 7 contro IIS 6 (cioè in IIS 6, 'Response.End()' lancia solo una 'ThreadAbortException' che l'autenticazione dei form il modulo stava recuperando - il che significa che ha fatto ancora il reindirizzamento logico per me). –

+0

Ottimo questo ha funzionato! –

4

quello che hai scoperto è corretta circa le forme auth intercettare il 401 e facendo un redirect, ma anche possiamo fare per invertire tale.

Fondamentalmente quello che ti serve è un modulo http per intercettare il reindirizzamento 302 alla pagina di login e invertirlo a un 401.

Passaggi su farlo è spiegato in here

Il dato di collegamento è di circa un servizio WCF, ma è lo stesso in tutti gli scenari le forme auth.

Come spiegato nel link precedente è necessario cancellare anche le intestazioni http, ma ricordarsi di riportare l'intestazione del cookie alla risposta se la risposta originale (ossia prima dell'intercettazione) conteneva alcun cookie.

+0

Hmm, sì, è un'idea interessante! Certo, ho già una soluzione che funziona per me, ma sono sicuro che qualcun altro potrebbe trovare questa soluzione migliore. Grazie! –

+0

Sembra un trucco che potrebbe avere effetti collaterali altrettanto strani! –

5

Non so come questo Response.End() ha funzionato per voi. L'ho provato senza alcuna gioia, quindi ho esaminato MSDN per Response.End(): "interrompe l'esecuzione della pagina e solleva l'evento EndRequest".

Per quel che vale il mio mod è stato:

_response.StatusCode = 401; 
_context.Items["401Override"] = true; 
_response.End(); 

Poi nel Global.cs aggiungere un gestore EndRequest (che andranno chiamato dopo l'autenticazione HTTPModule):

protected void Application_EndRequest(object sender, EventArgs e) 
{ 
    if (HttpContext.Current.Items["401Override"] != null) 
    { 
     HttpContext.Current.Response.Clear(); 
     HttpContext.Current.Response.StatusCode = 401; 
    } 
} 
+0

Nella pagina, ho usato 'StatusCode = 418' e ho aggiunto il testo di override a' StatusDescription'. Quindi ho catturato quelli nell'evento EndRequest in cui ho azzerato la risposta come hai fatto tu. Se utilizzo il codice di stato 401 nella pagina, viene comunque reindirizzato. –

5

So che c'è già una risposta con tick ma mentre cercavo di risolvere un problema simile ho trovato this (http://blog.inedo.com/2010/10/12/http-418-im-a-teapot-finally-a-%e2%80%9clegitimate%e2%80%9d-use/) in alternativa.

In pratica si restituisce il proprio codice di stato HTTP (ad esempio 418) nel codice. Nel mio caso un servizio dati WCF.

throw new DataServiceException(418, "401 Unauthorized"); 

Poi utilizzare un modulo HTTP di gestirlo durante l'evento EndRequest per riscrivere il codice di nuovo a 401.

HttpApplication app = (HttpApplication)sender; 
if (app.Context.Response.StatusCode == 418) 
{ 
    app.Context.Response.StatusCode = 401; 
} 

Il browser/cliente riceverà il contenuto corretto e codice di stato, funziona benissimo per me :)

Se sei interessato a saperne di più sul codice di stato HTTP 418, consulta this question & answer.

+0

Ottimo! Funziona come voglio che funzioni :) aggiungendo invece il modulo http dedicato ho usato l'evento "global.asax" EndRequest. È possibile aggancio su questo evento che sovrascrive il metodo "Init" –

+0

questo restituisce solo la pagina di errore di IIS 401 per me – DevDave

+0

Grazie per aver menzionato i codici di stato personali: Poiché stavo cercando di restituire 401 per una singola pagina che ha verificato l'autenticazione tramite Chiamata AJAX, sono stato in grado di cambiare "Response.StatusCode'" non riuscito "su quella pagina a 418 per evitare tutti gli altri metodi! IIS7 si lamenta, ma nessun essere umano avrebbe bisogno di visitare quella pagina. – Tukaro

0

Non si imposta l'intestazione WWW-Authenticate nel codice visualizzato, quindi il client non può eseguire l'autenticazione HTTP anziché l'autenticazione dei moduli. Se questo è il caso, dovresti usare 403 anziché 401, che non saranno intercettati dallo FormsAuthenticaitonModule.

7

Per costruire leggermente la risposta di zacharydl, l'ho usata per risolvere i miei problemi. Ad ogni richiesta, all'inizio, se si tratta di AJAX, elimina immediatamente il comportamento.

protected void Application_BeginRequest() 
{ 
    HttpRequestBase request = new HttpRequestWrapper(Context.Request); 
    if (request.IsAjaxRequest()) 
    { 
     Context.Response.SuppressFormsAuthenticationRedirect = true; 
    } 
} 
Problemi correlati