asp.net-mvc-4 membership-provider

asp.net mvc 4 - El token antifalsificación está destinado para el usuario "" pero el usuario actual es "nombre de usuario"



asp.net-mvc-4 membership-provider (9)

Estoy compilando una aplicación de una sola página y estoy experimentando un problema con tokens antifalsificación.

Sé por qué ocurre el problema, simplemente no sé cómo solucionarlo.

Me sale el error cuando sucede lo siguiente:

  1. El usuario que no inició sesión carga un diálogo (con un token antifalsificación generado)
  2. El usuario cierra el diálogo
  3. El usuario inicia sesión
  4. El usuario abre el mismo diálogo
  5. El usuario envía el formulario en el diálogo

El token antifalsificación está destinado para el usuario "" pero el usuario actual es "nombre de usuario"

La razón por la que esto ocurre es porque mi aplicación es 100% de una sola página, y cuando un usuario inicia sesión exitosamente a través de una publicación de ajax en /Account/JsonLogin , simplemente cambio las vistas actuales con las "vistas autenticadas" devueltas desde el servidor pero no recargues la página

Sé que esta es la razón porque si simplemente recargo la página entre los pasos 3 y 4, no hay ningún error.

Por lo tanto, parece que @Html.AntiForgeryToken() en el formulario cargado todavía devuelve un token para el usuario anterior hasta que la página se vuelva a cargar.

¿Cómo puedo cambiar @Html.AntiForgeryToken() para devolver un token para el nuevo usuario autenticado?

IIdentity un nuevo GenericalPrincipal con un IIdentity personalizado en cada Application_AuthenticateRequest así que cuando @Html.AntiForgeryToken() se llama HttpContext.Current.User.Identity es, de hecho, mi identidad personalizada con la propiedad IsAuthenticated establecida en true y aún @Html.AntiForgeryToken todavía parece representar un token para el usuario anterior, a menos que realice una recarga de página.


El mensaje aparece cuando inicias sesión cuando ya estás autenticado.

Este Ayudante hace exactamente lo mismo que el atributo [ValidateAntiForgeryToken] .

System.Web.Helpers.AntiForgery.Validate()

Elimine el atributo [ValidateAntiForgeryToken] del controlador y coloque este helper en el método de acción.

Por lo tanto, cuando el usuario ya esté autenticado, redirija a la página de inicio o, si no lo hace, continúe con la verificación del token antifalsificación válido después de esta verificación.

if (User.Identity.IsAuthenticated) { return RedirectToAction("Index", "Home"); } System.Web.Helpers.AntiForgery.Validate();

Para tratar de reproducir el error, proceda de la siguiente manera: si está en su página de inicio de sesión y no está autenticado. Si duplica la pestaña e inicia sesión con la segunda pestaña. Y si vuelve a la primera pestaña en la página de inicio de sesión e intenta iniciar sesión sin volver a cargar la página ... tiene este error.


Esto sucede porque el token anti falsificación incorpora el nombre de usuario del usuario como parte del token cifrado para una mejor validación. Cuando llame por primera vez al @Html.AntiForgeryToken() el usuario no ha iniciado sesión, por lo que el token tendrá una cadena vacía para el nombre de usuario, después de que el usuario inicie sesión, si no reemplaza el token anti falsificación, no pasará la validación. porque el token inicial era para usuarios anónimos y ahora tenemos un usuario autenticado con un nombre de usuario conocido.

Usted tiene algunas opciones para resolver este problema:

  1. Solo esta vez permita que su SPA haga una POST completa y cuando la página se recargue, tendrá una ficha antifalsificación con el nombre de usuario actualizado incrustado.

  2. Tener una vista parcial con solo @Html.AntiForgeryToken() e inmediatamente después de iniciar sesión, haga otra solicitud AJAX y reemplace su token @Html.AntiForgeryToken() existente con la respuesta de la solicitud.

  3. Simplemente deshabilite la verificación de identidad que realiza la validación antifalsificación. Agregue lo siguiente a su método Application_Start : AntiForgeryConfig.SuppressIdentityHeuristicChecks = true .


Para corregir el error, debe colocar la anotación de datos de OutputCache en la página Get ActionResult of Login como:

[OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")] public ActionResult Login(string returnUrl)



Tenía un problema bastante específico pero similar dentro del proceso de registro. Una vez que el usuario hizo clic en el enlace de correo electrónico que se le envió, se iniciaría sesión y se le enviaría directamente a la pantalla de detalles de la cuenta para completar más información. Mi código era:

Dim result = Await UserManager.ConfirmEmailAsync(userId, code) If result.Succeeded Then Dim appUser = Await UserManager.FindByIdAsync(userId) If appUser IsNot Nothing Then Dim signInStatus = Await SignInManager.PasswordSignInAsync(appUser.Email, password, True, shouldLockout:=False) If signInStatus = SignInStatus.Success Then Dim identity = Await UserManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ApplicationCookie) AuthenticationManager.SignIn(New AuthenticationProperties With {.IsPersistent = True}, identity) Return View("AccountDetails") End If End If End If

Descubrí que Return View ("Detalles de la cuenta") me estaba dando la excepción del token, supongo que porque la función ConfirmEmail estaba decorada con AllowAnonymous pero la función AccountDetails tenía ValidateAntiForgeryToken.

Cambiar el retorno para regresar RedirectToAction ("AccountDetails") resolvió el problema por mí.


Tiene un problema con la validación anti-falsificación de tokens en internet-shop: los usuarios abren muchas pestañas (con productos) y después de iniciar sesión intentan iniciar sesión en otra y obtienen AntiForgeryException. Entonces, AntiForgeryConfig.SuppressIdentityHeuristicChecks = true no me ayudó, así que usé un hackfix tan feo, tal vez sea útil para alguien:

public class ExceptionPublisherExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext exceptionContext) { var exception = exceptionContext.Exception; var request = HttpContext.Current.Request; if (request != null) { if (exception is HttpAntiForgeryException && exception.Message.ToLower().StartsWith("the provided anti-forgery token was meant for user /"/", but the current user is")) { var isAjaxCall = string.Equals("XMLHttpRequest", request.Headers["x-requested-with"], StringComparison.OrdinalIgnoreCase); var returnUrl = !string.IsNullOrWhiteSpace(request["returnUrl"]) ? request["returnUrl"] : "/"; var response = HttpContext.Current.Response; if (isAjaxCall) { response.Clear(); response.StatusCode = 200; response.ContentType = "application/json; charset=utf-8"; response.Write(JsonConvert.SerializeObject(new { success = 1, returnUrl = returnUrl })); response.End(); } else { response.StatusCode = 200; response.Redirect(returnUrl); } } } ExceptionHandler.HandleException(exception); } } public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new ExceptionPublisherExceptionFilter()); filters.Add(new HandleErrorAttribute()); } }

Piensa que sería genial si se pueden establecer opciones de generación de token antifalsificación para excluir un nombre de usuario o algo así.


Tuve el mismo problema con una aplicación ASP.NET MVC Core de una sola página. Lo resolví configurando HttpContext.User en todas las acciones del controlador que cambian las afirmaciones de identidad actuales (ya que MVC solo hace esto para solicitudes posteriores, como se explica here ). Usé un filtro de resultados en lugar de middleware para agregar las cookies antiforgery a mis respuestas, lo que me aseguró de que solo se generaran después de que la acción de MVC había regresado.

Controlador (NB. Estoy administrando usuarios con ASP.NET Core Identity):

[Authorize] [ValidateAntiForgeryToken] public class AccountController : Controller { private SignInManager<IdentityUser> signInManager; private UserManager<IdentityUser> userManager; private IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory; public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory) { this.signInManager = signInManager; this.userManager = userManager; this.userClaimsPrincipalFactory = userClaimsPrincipalFactory; } [HttpPost] [AllowAnonymous] public async Task<IActionResult> Login(string username, string password) { if (username == null || password == null) { return BadRequest(); // Alias of 400 response } var result = await signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false); if (result.Succeeded) { var user = await userManager.FindByNameAsync(username); // Must manually set the HttpContext user claims to those of the logged // in user. Otherwise MVC will still include a XSRF token for the "null" // user and token validation will fail. (MVC appends the correct token for // all subsequent reponses but this isn''t good enough for a single page // app.) var principal = await userClaimsPrincipalFactory.CreateAsync(user); HttpContext.User = principal; return Json(new { username = user.UserName }); } else { return Unauthorized(); } } [HttpPost] public async Task<IActionResult> Logout() { await signInManager.SignOutAsync(); // Removing identity claims manually from the HttpContext (same reason // as why we add them manually in the "login" action). HttpContext.User = null; return Json(new { result = "success" }); } }

Filtro de resultados para agregar cookies antiforgery:

public class XSRFCookieFilter : IResultFilter { IAntiforgery antiforgery; public XSRFCookieFilter(IAntiforgery antiforgery) { this.antiforgery = antiforgery; } public void OnResultExecuting(ResultExecutingContext context) { var HttpContext = context.HttpContext; AntiforgeryTokenSet tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext); HttpContext.Response.Cookies.Append( "MyXSRFFieldTokenCookieName", tokenSet.RequestToken, new CookieOptions() { // Cookie needs to be accessible to Javascript so we // can append it to request headers in the browser HttpOnly = false } ); } public void OnResultExecuted(ResultExecutedContext context) { } }

Extracto de Startup.cs:

public partial class Startup { public Startup(IHostingEnvironment env) { //... } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { //... services.AddAntiforgery(options => { options.HeaderName = "MyXSRFFieldTokenHeaderName"; }); services.AddMvc(options => { options.Filters.Add(typeof(XSRFCookieFilter)); }); services.AddScoped<XSRFCookieFilter>(); //... } public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //... } }


Tuve el mismo problema, y ​​este truco sucio lo solucionó, al menos hasta que pueda solucionarlo de una manera más limpia.

public ActionResult Login(string returnUrl) { if (AuthenticationManager.User.Identity.IsAuthenticated) { AuthenticationManager.SignOut(); return RedirectToAction("Login"); }

...


[OutputCache(NoStore=true, Duration = 0, VaryByParam= “None”)] public ActionResult Login(string returnUrl)

Puede probar esto poniendo un punto de corte en la primera línea de su acción de Inicio de sesión (Obtener). Antes de agregar la directiva OutputCache, el punto de interrupción se activaría en la primera carga, pero después de hacer clic en el botón de retroceso del navegador no lo haría. Después de agregar la directiva, debe terminar con el punto de interrupción siendo golpeado cada vez, por lo que AntiForgeryToken será el principal, no el vacío.