c# - route - Una forma de manejar adecuadamente HttpAntiForgeryException en la aplicación MVC 4
mvc route attribute (4)
Debería poder manejar la excepción agregando un filtro de acción para manejar su error.
[HandleError(View="AntiForgeryExceptionView", ExceptionType = typeof(HttpAntiForgeryException))]
Todo, así que asegúrese de que los errores personalizados estén activados en su web.config.
<customErrors mode="On"/>
También puedes echar un vistazo a este blog para obtener más información sobre el error de manejo.
Editar Puesto que está utilizando MVC4 y el blog es sobre MVC3, también podría echar un vistazo a la biblioteca de MSDN - HandleErrorAttribute , pero la versión no debería marcar la diferencia.
Aquí está el escenario:
Tengo una página de inicio de sesión, cuando el usuario la firma se redirige a la página de la aplicación de inicio. Luego, el usuario está usando el botón Atrás del navegador, y ahora está en la página de inicio de sesión. Intenta iniciar sesión de nuevo, pero ahora se lanza una excepción:
HttpAntiForgeryException (0x80004005): el token antifalsificación proporcionado estaba destinado para el usuario "", pero el usuario actual es "nombre de usuario".
Sé que esto está relacionado con el almacenamiento en caché. Inhabilité el almacenamiento en caché del navegador para la acción de inicio de sesión utilizando el filtro NoCache personalizado que establece todos los encabezados necesarios: sin caché, sin almacenamiento, sin necesidad de revalidación, etc.
- esto no funciona en todos los navegadores
- especialmente Safari (móvil en la mayoría de los casos) ignora por completo estos ajustes
Trataré de hackear y forzar el safari móvil para actualizar, pero esto no es lo que estoy esperando.
Me gustaría saber si puedo:
- manejar la excepción sin mostrar al usuario ningún problema existe (totalmente transparente para el usuario)
- evite este problema reemplazando el nombre de usuario del token anti falsificación que permitirá el inicio de sesión del usuario nuevamente sin esta excepción, si mis hacks relacionados con el almacenamiento en caché del navegador dejarán de funcionar en las próximas versiones de los navegadores.
- Realmente me gustaría no confiar en el comportamiento del navegador, ya que cada uno se comporta de manera diferente.
ACTUALIZACIÓN 1
Para hacer algunas aclaraciones, sé cómo manejar los errores en MVC. El problema es que estos errores de manejo no solucionan mi problema en absoluto. La idea básica de manejo de errores es redirigir a la página de error personalizado con un bonito mensaje. Pero quiero evitar que ocurra este error, no manejarlo de forma visible para el usuario. Por handle me refiero a catch make username replace u otra acción adecuada, luego continúe ingresando.
ACTUALIZACIÓN 2
He agregado la solución a continuación que funciona para mí.
Después de un tiempo de investigación, creo que encontré la forma de deshacerme de este error para el usuario. No es perfecto, pero al menos no muestra la página de error:
HandleErrorAttribute
filtro basado en HandleErrorAttribute
:
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes",
Justification = "This attribute is AllowMultiple = true and users might want to override behavior.")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class LoginAntiforgeryHandleErrorAttribute : FilterAttribute, IExceptionFilter
{
#region Implemented Interfaces
#region IExceptionFilter
/// <summary>
/// </summary>
/// <param name="filterContext">
/// The filter context.
/// </param>
/// <exception cref="ArgumentNullException">
/// </exception>
public virtual void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.IsChildAction)
{
return;
}
// If custom errors are disabled, we need to let the normal ASP.NET exception handler
// execute so that the user can see useful debugging information.
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
{
return;
}
Exception exception = filterContext.Exception;
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500)
{
return;
}
// check if antiforgery
if (!(exception is HttpAntiForgeryException))
{
return;
}
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" }
});
filterContext.ExceptionHandled = true;
}
#endregion
#endregion
}
Luego apliqué este filtro a la acción POST de inicio de sesión:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[LoginAntiforgeryHandleError]
public ActionResult Login(Login model, string returnUrl)
{
La idea principal de esta solución es redirigir la excepción antifalsificación a la acción del índice principal. Si el usuario aún no estará sin autenticar, mostrará la página de inicio de sesión si el usuario ya estará autenticado y mostrará la página de índice .
ACTUALIZACIÓN 1 Hay un problema potencial con esta solución. Si alguien está iniciando sesión con credenciales diferentes, entonces en caso de error debería agregarse el tiempo de ejecución de inicio de sesión adicional: cierre la sesión del usuario anterior e inicie sesión en uno nuevo. Este escenario no es manejado.
Si solo tiene una o algunas funciones afectadas, la creación de un filtro puede ser una exageración ligeramente técnica. Una solución más simple pero no genérica es simplemente eliminar el [ValidateAntiForgeryToken]
para el método específico y agregar una validación manual después de verificar si el usuario está conectado.
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Home");
}
System.Web.Helpers.AntiForgery.Validate();
/* proceed with authentication here */
Una vieja pregunta, pero me encontré con este problema hoy, y la forma en que lo resolví fue redireccionando a la acción de cerrar la sesión de la siguiente manera:
public ActionResult Login(string returnUrl)
{
if (WebSecurity.IsAuthenticated)
return RedirectToAction("LogOff");
...
}