2010-04-07 24 views
14

Tenemos una aplicación ASP.NET MVC interna que requiere un inicio de sesión. Iniciar sesión funciona muy bien y hace lo que se espera. Tenemos una sesión de vencimiento de 15 minutos. Después de estar sentado en una sola página durante ese período de tiempo, el usuario ha perdido la sesión. Si intentan actualizar la página actual o buscar otra, obtendrán una página de inicio de sesión. Mantenemos su solicitud almacenada para que una vez que hayan iniciado sesión puedan continuar a la página que han solicitado. Esto funciona genialASP.NET MVC Expiración de la sesión

Sin embargo, mi problema es que en algunas páginas hay llamadas AJAX. Por ejemplo, pueden completar parte de un formulario, deambular y dejar que expire su sesión. Cuando vuelven, la pantalla todavía se muestra. Si simplemente completan una casilla (que hará una llamada AJAX), la llamada AJAX devolverá la página de Inicio de sesión (dentro de cualquier div que el AJAX debería haber devuelto simplemente los resultados reales). Esto se ve horrible.

Creo que la solución es hacer que la página caduque (de modo que cuando una sesión finaliza, automáticamente se devuelven a la pantalla de inicio de sesión sin ninguna acción de ellos). Sin embargo, me pregunto si hay opiniones/ideas sobre la mejor manera de implementar esto específicamente con respecto a las mejores prácticas en ASP.NET MVC.

Actualización:

Así que siguió adelante e implementado esta en mi OnActionExecuting (por sugerencia de 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 
    { 
     ... 
    } 
    } 

Esto definitivamente hace las cosas mejor - ahora incluso si tienen dos pestañas (una con algunas llamadas AJAX que pueden desencadenar) y se desconectan de forma explícita en la segunda pestaña, inmediatamente obtendrán algo que tiene más sentido en lugar de un montón de datos AJAX estropeados.

Todavía creo que implementaré la cuenta regresiva de Javascript, así como sugirió womp.

+0

@Andrew - Esa es una solución elegante. Alernatively, would filterContext.HttpContext.Response.Redirect ("/ error/xxx"); (o algo) trabajo? – Keltex

+0

@Keltex: puedo moverlo a una vista como sugieres, pero en muchas de mis llamadas AJAX, devuelven datos brutos (como una lista de valores) sin HTML, mientras que en otros pueden devolver una tabla completa de datos muy formateados.Por lo tanto, un "mínimo común denominador" de solo datos en bruto puede funcionar mejor. Jugaré con eso. –

Respuesta

16

Específicamente, no sé si hay algunas mejores prácticas al respecto, pero estoy haciendo esto ahora mismo para nuestra aplicación. Hemos optado por una solución del lado del cliente donde generamos el valor de tiempo de espera de la sesión en algunos javascript en la página maestra y calculamos cuándo caducará la sesión.

5 minutos antes, aparece un cuadro de diálogo modal que dice "¿Sigues ahí?" con un temporizador de cuenta regresiva. Una vez que el temporizador llega a las 0:00, redirigimos el navegador a la página de inicio de sesión.

Se implementa con una mínima cantidad de javascript para hacer los cálculos de tiempo y temporizador, y un sencillo controlador .ashx que actualizará la sesión si el usuario hace clic en "¡Estoy de vuelta!" en el cuadro de diálogo antes de que caduque la sesión. De esta forma, si regresan a tiempo, pueden actualizar la sesión sin ninguna navegación.

+2

+1: Esta sería una solución con menor impacto y menos código que la comprobación de la sesión con cada solicitud. –

+2

Eso es muy bueno. Voy a embaucar esto. – Will

+0

Tuve que buscar lo que significaba "gank" ... :-) Creo que voy a implementar esto además de proporcionar algunas salvaguardas AJAX por sugerencia de Keltex. ¡Gracias! –

2

Puede buscar en las AjaxOptions que se pueden establecer en Ajax.BeginForm(). Hay una configuración OnBegin que puede asociar con una función de JavaScript, que podría llamar a un método de Controlador para confirmar que la sesión sigue siendo válida, y si no, redirigir a la página de inicio de sesión usando window.location.

1

Parte del problema parece ser que está dejando que el marco haga todo. No decoraría su método AJAX con el atributo [Authorize]. En su lugar, marque User.Identity.IsAuthenticated y si devuelve falso, cree un mensaje de error sensible.

+0

¡Gracias! Implementé esto en OnActionExecuting (ver arriba). –

+0

El atributo Autorizar está bien, pero debe modificarlo correctamente. Comprobar 'User.Identity.IsAuthenticated' no es lo mismo. – LukLed

7

Hice una pregunta similar ayer.Aquí está mi solución:

Modificado atributo Autorizar:

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 se anula por lo que vuelve Http403Result cuando se utiliza Ajax. Http403Result cambia StatusCode a 403 y devuelve un mensaje al usuario en respuesta. Hay alguna lógica adicional en el atributo (parámetro authorize), porque enciendo [Authorize] en el controlador base y lo deshabilito en algunas páginas.

Otra parte importante es el manejo global de esta respuesta en el lado del cliente. Esto es lo que he puesto en 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> 

coloco gestor de errores ajax global y cuando Evert $.post falla con el error 403, mensaje de respuesta es alertado y se redirige al usuario cerrar la sesión página. Ahora no tengo que manejar el error en cada solicitud $.post, ya que se maneja de forma global.

¿Por qué 403, no 401? 401 es manejado internamente por MVC framework (es por eso que la redirección a la página de inicio de sesión se realiza después de la autorización fallida).

¿Qué opina sobre eso?

EDIT:

Acerca de renunciar a [Autorizar] atributo: [Autorizar] no es sólo acerca de la comprobación Identity.IsAuthenticated. También maneja el almacenamiento en caché de páginas (para que no cachee el material que requiere autenticación) y la redirección. No hay necesidad de copiar este código.

0

Mi solución utiliza una metaetiqueta en el formulario de inicio de sesión y un poco de 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; 
    } 
}; 

código 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

Así es como lo hice .. .

En mi controlador 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(); 
      } 
     } 
    } 

Luego, en mis Js global Archivo

$.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 variable SessionTimeout es una cadena noty. Omití la implementación por brevedad.

Cuestiones relacionadas