asp.net - tokens - token csrf
¿Cómo se manejan las solicitudes ajax cuando el usuario no está autenticado? (6)
Aquí hay una solución que uso. Es muy simple, si un poco de fuerza bruta. Me gusta porque soy flojo y no quiero pensar en atributos especiales en los métodos de acción y no quiero escribir controladores de errores ajax si no tengo que hacerlo (aunque no hay razón para que el script del cliente no pueda detectar el código de estado 403 y hacer algo fácil de usar).
Al poner esto en Global.axax, se detecta cualquier solicitud ajax no autenticada y simplemente se devuelve 403, sin contenido. Esto evita que las llamadas ajax no autenticadas sean redirigidas al formulario de inicio de sesión cuando la autenticación de formularios está en uso.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Prevent Ajax requests from being returned the login form when not authenticated
// (eg. after authentication timeout).
if ((Request.Headers["X-Requested-With"] != null && Request.Headers["X-Requested-With"] == "XMLHttpRequest")
||
(Request["X-Requested-With"] != null && Request["X-Requested-With"] == "XMLHttpRequest"))
{
if (!Request.IsAuthenticated)
{
Response.Clear();
Response.StatusCode = 403;
Response.Flush();
Response.End();
}
}
}
¿Cómo se manejan las solicitudes ajax cuando el usuario no está autenticado?
Alguien entra a la página, deja espacio durante una hora, regresa, agrega comentarios en la página que recorre ajax usando jQuery ( $.post
). Como no está autenticado, el método devuelve el resultado de RedirectToRoute (redirige a la página de inicio de sesión). Qué haces con eso? ¿Cómo se maneja en el lado del cliente y cómo se maneja en el controlador?
La idea que surgió cuando un compañero de trabajo me preguntó cómo manejarla fue esta: crear un atributo AuthorizeAjax. Puede interrogar y verificar que Request.IsAjaxRequest () y, si la solicitud no está autenticada, devolver un objeto de error JSON específico. Es posible que simplemente anule el atributo AuthorizeAttribute predeterminado y haga que llame a la base a menos que se trate de una solicitud AJAX no autorizada, por lo que no debe preocuparse por etiquetar las acciones del controlador con [Authorize] o [AuthorizeAjax].
En el lado del cliente, todas sus páginas deberían estar equipadas para lidiar con el error devuelto, pero esa lógica probablemente pueda ser compartida.
La solución más simple y limpia que he encontrado para esto es registrar una devolución de llamada con el evento jQuery.ajaxSuccess () y verificar el encabezado de respuesta "X-AspNetMvc-Version".
Cada solicitud jQuery Ajax en mi aplicación es manejada por Mvc así que si falta el encabezado sé que mi solicitud ha sido redireccionada a la página de inicio de sesión, y simplemente recargo la página para un redireccionamiento de alto nivel:
$(document).ajaxSuccess(function(event, XMLHttpRequest, ajaxOptions) {
// if request returns non MVC page reload because this means the user
// session has expired
var mvcHeaderName = "X-AspNetMvc-Version";
var mvcHeaderValue = XMLHttpRequest.getResponseHeader(mvcHeaderName);
if (!mvcHeaderValue) {
location.reload();
}
});
La recarga de la página puede causar algunos errores de Javascript (dependiendo de lo que esté haciendo con la respuesta de Ajax) pero en la mayoría de los casos en que la depuración está desactivada, el usuario nunca los verá.
Si no desea utilizar el encabezado integrado, estoy seguro de que puede agregar fácilmente uno personalizado y seguir el mismo patrón.
Propondría crear tu propio AuthorizeAttribute y si la solicitud es una solicitud de Ajax, lanza una HttpException (401/403). Y también cambie para usar el Método Ajax de jQuery .
Suponiendo que ha implementado páginas de error y devuelven el código de estado correcto, se ejecutará la devolución de llamada de error
lugar de la devolución de llamada success
. Esto sucederá debido al código de respuesta.
Puede detectar la solicitud ajax y enviar 401, y del lado del cliente puede mostrar un cuadro de diálogo ajax con solicitud de inicio de sesión, después de lo cual puede "continuar" su solicitud ajax fallida y hacer que la aplicación funcione y el usuario sienta que nunca se ha agotado el tiempo de espera de la sesión. Vea esta respuesta para más detalles.
EDITAR:
Escribí la respuesta anterior hace mucho tiempo y ahora creo que enviar 403 no es el camino correcto. 403 tiene un significado ligeramente diferente y simplemente no debe usarse. Este es un atributo corregido usando 401. Solo difiere con el context.HttpContext.Response.End()
adicional. context.HttpContext.Response.End()
en Http401Result y diferentes códigos HTTP:
public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
private class Http401Result : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 401.
context.HttpContext.Response.StatusCode = 401;
context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
context.HttpContext.Response.End();
}
}
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 401 error.
filterContext.Result = new Http401Result();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
ANTIGUA RESPUESTA:
Si bien me gustan las ideas publicadas en otras respuestas (sobre las cuales tuve una idea anterior), necesitaba ejemplos de código. Aquí están:
Atributo de Autorización Modificada:
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 reemplaza, por lo que devuelve Http403Result
cuando se usa Ajax. Http403Result
cambia StatusCode a 403 y devuelve un mensaje al usuario en respuesta. Existe alguna lógica adicional en el atributo (parámetro authorize
), porque activo [Authorize]
en el controlador base y lo desactivo en algunas páginas.
La otra parte importante es el manejo global de esta respuesta en el lado del cliente. Esto es lo que coloqué 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 un manejador de errores ajax GLOBAL y cuando $.post
falla con un error 403, el mensaje de respuesta es alertado y el usuario es redirigido a la página de cierre de sesión. Ahora no tengo que manejar el error en cada solicitud $.post
, porque se maneja de forma global.
¿Por qué 403 y 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é piensa usted al respecto?