2010-04-07 26 views
14

Abbiamo un'applicazione ASP.NET MVC interna che richiede un accesso. Accedi funziona alla grande e fa ciò che è previsto. Abbiamo una scadenza di sessione di 15 minuti. Dopo essersi seduto su una singola pagina per quel periodo di tempo, l'utente ha perso la sessione. Se tentano di aggiornare la pagina corrente o di passare a un'altra, otterranno una pagina di accesso. Manteniamo la richiesta archiviata, quindi, una volta effettuato l'accesso, possono continuare a visitare la pagina richiesta. Funziona alla grandeScadenza sessione MVC ASP.NET

Tuttavia, il mio problema è che su alcune pagine ci sono chiamate AJAX. Ad esempio, possono compilare parte di un modulo, vagare e lasciare scadere la sessione. Quando tornano, lo schermo è ancora visualizzato. Se si limita a compilare una casella (che effettuerà una chiamata AJAX), la chiamata AJAX restituirà la pagina di accesso (all'interno di qualsiasi div l'AJAX avrebbe dovuto semplicemente restituire i risultati effettivi). Questo sembra orribile.

Penso che la soluzione sia quella di far scadere la pagina stessa (in modo tale che quando una sessione viene terminata, vengono automaticamente restituiti alla schermata di accesso senza alcuna azione da parte loro). Tuttavia, mi chiedo se ci sono opinioni/idee su come meglio implementare questo in particolare per quanto riguarda le migliori pratiche in ASP.NET MVC.

Aggiornamento:

Così sono andato avanti e implementato questo nel mio OnActionExecuting (per suggerimento di Keltex)

if (!filterContext.HttpContext.User.Identity.IsAuthenticated) 
    { 
    if (filterContext.HttpContext.Request.IsAjaxRequest()) 
    { 
     filterContext.HttpContext.Response.Write("Invalid session -- please login!"); 
     filterContext.HttpContext.Response.End(); 
    } 
    else 
    { 
     ... 
    } 
    } 

Questo rende sicuramente meglio le cose - ora anche se hanno due schede (una con alcune chiamate AJAX che possono attivare) e si disconnettono esplicitamente nella seconda scheda, otterranno immediatamente qualcosa che ha più senso piuttosto che un mucchio di dati AJAX rovinati.

Continuo a pensare che implementerò anche il conteggio alla rovescia di Javascript che suggeriva Womp.

+0

@Andrew - Questa è una soluzione elegante. Altrimenti, filterContext.HttpContext.Response.Redirect ("/ error/xxx"); (o qualcosa) funziona? – Keltex

+0

@Keltex: posso spostarlo su una vista come suggerito, ma in molte delle mie chiamate AJAX, restituiscono dati non elaborati (come un elenco di valori) senza HTML, mentre in altri restituiscono forse un'intera tabella di dati ben formattati.Quindi un "minimo comune denominatore" di soli dati grezzi può funzionare meglio. Ci giocherò. –

risposta

16

In particolare, non so che ci siano delle migliori pratiche in merito, ma lo sto facendo ora per la nostra app. Abbiamo optato per una soluzione lato client in cui emettiamo il valore di timeout della sessione in qualche javascript nella pagina principale e calcoliamo quando la sessione scadrà.

5 minuti prima mano, si apre una finestra di dialogo modale che dice "Sei ancora lì?" con un conto alla rovescia. Quando il timer raggiunge 0:00, reindirizziamo il browser alla pagina di accesso.

È implementato con una quantità minima di javascript per eseguire i calcoli di tempo e timer e un semplice gestore .ashx che aggiornerà la sessione se l'utente fa clic su "Sono tornato!" nella finestra di dialogo prima della scadenza della sessione. In questo modo, se ritornano in tempo, possono aggiornare la sessione senza alcuna navigazione.

+2

+1: questa sarebbe una soluzione a basso impatto e meno codice rispetto alla verifica di ogni richiesta. –

+2

È molto bello. Sto andando completamente a gank questo. – Will

+0

Dovevo cercare cosa significasse "gank" ... :-) Penso che implementerò questo aspetto oltre a fornire alcune protezioni AJAX per suggerimento di Keltex. Grazie! –

2

Si potrebbe esaminare AjaxOptions che può essere impostato in Ajax.BeginForm(). È disponibile un'impostazione OnBegin che è possibile associare a una funzione javascript, che potrebbe chiamare un metodo Controller per confermare che la sessione è ancora valida e, in caso contrario, reindirizzare alla pagina di accesso utilizzando window.location.

1

Parte del problema sembra essere che si sta lasciando che il framework faccia tutto. Non vorrei decorare il tuo metodo AJAX con l'attributo [Authorize]. Controllare invece User.Identity.IsAuthenticated e se restituisce false, creare un messaggio di errore ragionevole.

+0

Grazie! L'ho implementato in OnActionExecuting (vedi sopra). –

+0

L'attributo Authorize è OK, ma è necessario modificarlo correttamente. Controllare 'User.Identity.IsAuthenticated' non è lo stesso. – LukLed

7

Ho fatto una domanda simile ieri.Qui è la mia soluzione:

Modificato attributo Autorizza:

public class OptionalAuthorizeAttribute : AuthorizeAttribute 
{ 
    private class Http403Result : ActionResult 
    { 
     public override void ExecuteResult(ControllerContext context) 
     { 
      // Set the response code to 403. 
      context.HttpContext.Response.StatusCode = 403; 
      context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue); 
     } 
    } 

    private readonly bool _authorize; 

    public OptionalAuthorizeAttribute() 
    { 
     _authorize = true; 
    } 

    //OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller. 
    //That is why parameter is introduced. 
    public OptionalAuthorizeAttribute(bool authorize) 
    { 
     _authorize = authorize; 
    } 

    protected override bool AuthorizeCore(HttpContextBase httpContext) 
    { 
     //When authorize parameter is set to false, not authorization should be performed. 
     if (!_authorize) 
      return true; 

     var result = base.AuthorizeCore(httpContext); 

     return result; 
    } 

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) 
    { 
     if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) 
     { 
      //Ajax request doesn't return to login page, it just returns 403 error. 
      filterContext.Result = new Http403Result(); 
     } 
     else 
      base.HandleUnauthorizedRequest(filterContext); 
    } 
} 

HandleUnauthorizedRequest viene sovrascritto, quindi restituisce Http403Result quando si utilizza Ajax. Http403Result cambia StatusCode su 403 e restituisce un messaggio all'utente in risposta. C'è qualche logica aggiuntiva nell'attributo (parametro authorize), perché accendo [Authorize] nel controller di base e lo disattivo in alcune pagine.

Un'altra parte importante è la gestione globale di questa risposta sul lato client. Questo è quello che ho messo in Site.master:

<script type="text/javascript"> 
    $(document).ready(
     function() { 
      $("body").ajaxError(
       function(e,request) { 
        if (request.status == 403) { 
         alert(request.responseText); 
         window.location = '/Logout'; 
        } 
       } 
      ); 
     } 
    ); 
</script> 

ho posto GLOBAL gestore degli errori ajax e quando Evert $.post non riesce con 403 errori, messaggio di risposta viene allertato e l'utente viene reindirizzato alla pagina di logout. Ora non devo gestire l'errore in ogni richiesta $.post, perché è gestita globalmente.

Perché 403, non 401? 401 è gestito internamente dal framework MVC (ecco perché il reindirizzamento alla pagina di accesso viene effettuato dopo l'autorizzazione fallita).

Cosa ne pensi?

EDIT:

A proposito di dimissioni dal [Autorizza] attributo: [Autorizza] non è solo circa il controllo Identity.IsAuthenticated. Gestisce anche il caching delle pagine (in modo da non memorizzare nella cache il materiale che richiede l'autenticazione) e il reindirizzamento. Non è necessario copiare questo codice.

0

La mia soluzione utilizza un meta-tag sul modulo di login e un po 'di Javascript/jQuery.

LogOn.cshtml

<html> 
    <head> 
    <meta data-name="__loginform__" content="true" /> 
    ... 
    </head> 
    ... 
</html> 

Common.js

var Common = { 
    IsLoginForm: function (data) { 
     var res = false; 

     if (data.indexOf("__loginform__") > 0) { 
      // Do a meta-test for login form 
      var temp = 
       $("<div>") 
        .html(data) 
        .find("meta[data-name='__loginform__']") 
        .attr("content"); 

      res = !!temp; 
     } 
     return res; 
    } 
}; 

codice AJAX

$.get(myUrl, myData, function (serverData) { 
    if (Common.IsLoginForm(serverData)) { 
     location.reload(); 
     return; 
    } 

    // Proceed with filling your placeholder or whatever you do with serverData response 
    // ... 
}); 
0

Ecco come ho fatto .. .

Nel mio controller di base

protected override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     if (!filterContext.HttpContext.User.Identity.IsAuthenticated) 
     { 
      if (filterContext.HttpContext.Request.IsAjaxRequest()) 
      { 
       filterContext.HttpContext.Response.StatusCode = 403; 
       filterContext.HttpContext.Response.Write(SessionTimeout); 
       filterContext.HttpContext.Response.End(); 
      } 
     } 
    } 

poi nella mia .js globale del file

$.ajaxSetup({ 
error: function (x, status, error) { 
    if (x.status == 403) { 
     alert("Sorry, your session has expired. Please login again to continue"); 
     window.location.href = "/Account/Login"; 
    } 
    else { 
     alert("An error occurred: " + status + "nError: " + error); 
    } 
} 

});

La variabile SessionTimeout è una stringa noty. Ho omesso l'implementazione per brevità.

Problemi correlati