Le jeton anti-falsification est destiné à l'utilisateur "", mais l'utilisateur actuel est "nom d'utilisateur"


130

Je crée une application d'une seule page et rencontre un problème avec les jetons anti-contrefaçon.

Je sais pourquoi le problème se produit, je ne sais tout simplement pas comment le résoudre.

J'obtiens l'erreur lorsque ce qui suit se produit:

  1. L'utilisateur non connecté charge une boîte de dialogue (avec un jeton anti-contrefaçon généré)
  2. L'utilisateur ferme la boîte de dialogue
  3. L'utilisateur se connecte
  4. L'utilisateur ouvre la même boîte de dialogue
  5. L'utilisateur soumet le formulaire dans la boîte de dialogue

Le jeton anti-falsification est destiné à l'utilisateur "" mais l'utilisateur actuel est "username"

La raison pour laquelle cela se produit est que mon application est à 100% sur une seule page, et lorsqu'un utilisateur se connecte avec succès via un message ajax à /Account/JsonLogin, je change simplement les vues actuelles avec les "vues authentifiées" renvoyées par le serveur mais ne recharge pas le page.

Je sais que c'est la raison parce que si je recharge simplement la page entre les étapes 3 et 4, il n'y a pas d'erreur.

Il semble donc que @Html.AntiForgeryToken()dans le formulaire chargé renvoie toujours un jeton pour l'ancien utilisateur jusqu'à ce que la page soit rechargée.

Comment puis-je changer @Html.AntiForgeryToken()pour renvoyer un jeton pour le nouvel utilisateur authentifié?

J'injecte un nouveau GenericalPrincipalavec une coutume IIdentityà chaque Application_AuthenticateRequestfois que le temps @Html.AntiForgeryToken()est appelé HttpContext.Current.User.Identity, en fait mon identité personnalisée avec la IsAuthenticatedpropriété définie sur true et @Html.AntiForgeryTokensemble toujours restituer un jeton pour l'ancien utilisateur à moins que je ne recharge la page.


Pouvez-vous réellement vérifier que le code @ Html.AntiForgeryToken est appelé sans rechargement?
Kyle C

C'est définitivement le cas, je peux réussir une pause là-bas pour inspecter l'objet HttpContext.Current.User comme je l'ai mentionné
parlement

2
Veuillez vous référer à ceci: stackoverflow.com/a/19471680/193634
Rosdi Kasim

@parliament pourriez-vous s'il vous plaît dire quelle option avez-vous choisie dans la réponse ci-dessous.
Siddharth Pandey

Je crois que j'ai fait une exception pour aller avec un rechargement complet si je me souviens bien. Mais j'espère rencontrer ce problème très prochainement dans un nouveau projet. Je publierai si j'opte avec une meilleure option de travail.
parlement

Réponses:


170

Cela se produit parce que le jeton anti-contrefaçon intègre le nom d'utilisateur de l'utilisateur dans le cadre du jeton crypté pour une meilleure validation. Lorsque vous appelez pour la première fois, @Html.AntiForgeryToken()l'utilisateur n'est pas connecté, le jeton aura donc une chaîne vide pour le nom d'utilisateur, une fois que l'utilisateur se connectera, si vous ne remplacez pas le jeton anti-contrefaçon, il ne passera pas la validation car le jeton initial était pour utilisateur anonyme et nous avons maintenant un utilisateur authentifié avec un nom d'utilisateur connu.

Vous avez plusieurs options pour résoudre ce problème:

  1. Juste cette fois, laissez votre SPA faire un POST complet et lorsque la page se rechargera, il aura un jeton anti-contrefaçon avec le nom d'utilisateur mis à jour intégré.

  2. Ayez une vue partielle avec juste @Html.AntiForgeryToken()et juste après la connexion, faites une autre demande AJAX et remplacez votre jeton anti-falsification existant par la réponse de la demande.

  3. Désactivez simplement le contrôle d'identité effectué par la validation anti-falsification. Ajoutez ce qui suit à votre Application_Start méthode: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.


21
@parliament: vous avez accepté cette réponse, pourriez-vous nous dire quelle option vous avez choisie?
R. Schreurs

9
+1 pour l'option nice & simple 3. Les déconnexions chronométrées par les fournisseurs OAuth causent également ce problème.
Fini le codage

18
L'option 3 n'a pas fonctionné pour moi. Une fois déconnecté, j'ai ouvert deux fenêtres sur la page de connexion. Connecté en tant qu'un utilisateur dans une fenêtre, puis connecté en tant qu'un autre utilisateur dans l'autre et reçu la même erreur.
McGaz

5
Malheureusement, je n'ai pas pu trouver une bonne solution à cela. J'ai supprimé le jeton de la page de connexion. Je l'inclus toujours sur les messages après la connexion.
McGaz

7
L'option 3 n'a pas fonctionné pour moi non plus. Toujours la même erreur.
Joao Leme du

25

Pour corriger l'erreur, vous devez placer l' OutputCacheannotation de données sur la ActionResultpage Obtenir la connexion comme suit:

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

3
Cela a résolu le problème pour moi, c'est tout à fait logique. Merci!
Prime03

Mon cas d'utilisation était que l'utilisateur essayait de se connecter et obtenait une erreur, par exemple "compte désactivé" via ModelState.AddError (). Ensuite, s'ils cliquaient à nouveau sur la connexion, ils verraient cette erreur. Cependant, ce correctif leur a simplement donné une nouvelle vue de connexion vierge plutôt que l'erreur de jeton anti-falsification. Donc, pas de solution.
yourpublicdisplayname

Mon cas: 1. Connexion utilisateur () et atterrit sur la page d'accueil. 2. L'utilisateur appuie sur le bouton de retour et retourne à la vue de connexion. 3. Connectez-vous à nouveau et voyez l'erreur "Le jeton anti-falsification est destiné à l'utilisateur" "mais l'utilisateur actuel est" username "" Sur la page d'erreur Si l'utilisateur clique sur n'importe quel autre onglet du menu, l'application fonctionnait comme prévu . En utilisant le code ci-dessus, l'utilisateur peut toujours appuyer sur le bouton de retour, mais il est redirigé vers la page d'accueil. Ainsi, peu importe le nombre de fois que l'utilisateur clique sur le bouton de retour, il le redirigera vers la page d'accueil. Merci
Ravi

Des idées pourquoi cela ne fonctionne pas sur une vue Web Xamarin?
Noobie3001

1
Pour une explication complète, consultez l' amélioration des performances avec la mise en cache de sortie
stomy


8

J'ai eu le même problème, et ce sale hack l'a résolu, du moins jusqu'à ce que je puisse le résoudre de manière plus propre.

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

...


1
On dirait que j'ai eu le même problème. IMO ce n'est pas un hack, c'est plutôt une chose courante que nous devrions tous vérifier lors de la connexion. Si l'utilisateur est déjà connecté, déconnectez-le et affichez la page de connexion. Correction de mon problème, merci.
Alexandre

7

Le message apparaît lorsque vous vous connectez alors que vous êtes déjà authentifié.

Cet assistant fait exactement la même chose que l' [ValidateAntiForgeryToken]attribut.

System.Web.Helpers.AntiForgery.Validate()

Retirer le [ValidateAntiForgeryToken] attribut du contrôleur et placez cet assistant dans la méthode d'action.

Ainsi, lorsque l'utilisateur est déjà authentifié, redirigez vers la page d'accueil ou sinon continuez avec la vérification du jeton anti-falsification valide après cette vérification.

if (User.Identity.IsAuthenticated)
{
    return RedirectToAction("Index", "Home");
}

System.Web.Helpers.AntiForgery.Validate();

Pour essayer de reproduire l'erreur, procédez comme suit: Si vous êtes sur votre page de connexion et que vous n'êtes pas authentifié. Si vous dupliquez l'onglet et que vous vous connectez avec le deuxième onglet. Et si vous revenez au premier onglet de la page de connexion et que vous essayez de vous connecter sans recharger la page ... vous avez cette erreur.


Excellente solution! Cela a résolu mon problème après avoir essayé beaucoup d'autres suggestions qui n'ont pas fonctionné. Tout d'abord, c'était une douleur de reproduire l'erreur, jusqu'à ce que je découvre que cela pouvait être à cause de 2 navigateurs ou onglets ouverts avec la même page, et l'utilisateur se connectant à partir de l'un, puis se connectant à partir du second sans recharger.
Nicki

Merci pour cette solution. A travaillé pour moi aussi. J'ai ajouté une vérification pour voir si l'identité était la même que le nom d'utilisateur de connexion, et si c'est le cas, je continue avec plaisir à essayer de connecter l'utilisateur et de le déconnecter si ce n'est pas le cas. Par exemple, essayez {System.Web.Helpers.AntiForgery.Validate ();} catch (HttpAntiForgeryException) {if (! User.Identity.IsAuthenticated || string.Compare (User.Identity.Name, model.Username)! = 0) {// Votre logique de déconnexion ici}}
Steve Owen

2

J'ai la même exception qui se produit la plupart du temps sur le serveur de production.

Pourquoi cela arrive-t-il?

Cela se produit lorsque l'utilisateur se connecte avec des informations d'identification valides et une fois connecté et redirigé vers une autre page, et après avoir appuyé sur le bouton de retour, la page de connexion s'affiche et il a à nouveau entré des informations d'identification valides que cette exception se produira.

Comment résoudre?

Ajoutez simplement cette ligne et travaillez parfaitement, sans erreur.

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]

1

J'ai eu un problème assez spécifique mais similaire dans le processus d'inscription. Une fois que l'utilisateur a cliqué sur le lien e-mail qui lui a été envoyé, il était connecté et envoyé directement à un écran de détails de compte pour fournir plus d'informations. Mon code était:

    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

J'ai trouvé que la vue de retour ("AccountDetails") me donnait l'exception de jeton, je suppose parce que la fonction ConfirmEmail a été décorée avec AllowAnonymous mais la fonction AccountDetails avait ValidateAntiForgeryToken.

Changer le Return to Return RedirectToAction ("AccountDetails") a résolu le problème pour moi.


1
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")]

public ActionResult Login(string returnUrl)

Vous pouvez tester cela en mettant un point d'arrêt sur la première ligne de votre action Connexion (Obtenir). Avant d'ajouter la directive OutputCache, le point d'arrêt était atteint lors du premier chargement, mais après avoir cliqué sur le bouton de retour du navigateur, ce ne serait pas le cas. Après avoir ajouté la directive, vous devriez vous retrouver avec le point d'arrêt à chaque fois, donc l'AntiForgeryToken sera le corect, pas le vide.


0

J'ai eu le même problème avec une application ASP.NET MVC Core d'une seule page. Je l'ai résolu en définissant HttpContext.Usertoutes les actions du contrôleur qui modifient les revendications d'identité actuelles (puisque MVC ne le fait que pour les demandes ultérieures, comme indiqué ici ). J'ai utilisé un filtre de résultat au lieu d'un middleware pour ajouter les cookies anti-contrefaçon à mes réponses, ce qui m'a assuré qu'ils n'étaient générés qu'après le retour de l'action MVC.

Contrôleur (NB. Je gère les utilisateurs avec 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" });
    }
}

Filtre de résultat pour ajouter des cookies anti-contrefaçon:

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)
    {

    }
}

Extrait 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)
    {
        //...
    }
}

-3

A un problème avec la validation anti-falsification des jetons dans la boutique en ligne: les utilisateurs ouvrent de nombreux onglets (avec des marchandises) et après s'être connectés sur l'un essaient de se connecter sur un autre et ont obtenu une telle AntiForgeryException. Donc, AntiForgeryConfig.SuppressIdentityHeuristicChecks = true n'a pas aidé pour moi, alors j'ai utilisé un tel hackfix laid, peut-être que cela sera utile pour quelqu'un:

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

Je pense que ce sera génial si des options de génération de jetons anti-falsification peuvent être définies, pour exclure le nom d'utilisateur ou quelque chose comme ça.


12
C'est un exemple terrible de traitement du problème dans la question. N'utilisez pas ça.
xxbbcc

Totalement d'accord avec xxbbcc.
Javier

OK, cas d'utilisation: formulaire de connexion avec jeton anti-falsification. Ouvrez-le dans 2 onglets du navigateur. Connectez-vous d'abord. Vous ne pouvez pas actualiser le deuxième onglet. Quelle solution suggérez-vous pour avoir un comportement correct pour l'utilisateur qui essaie de se connecter à partir du deuxième onglet?
user3364244

@ user3364244: un comportement correct peut: détecter une connexion externe à l'aide de websockets ou de signalR. C'est la même session pour que vous puissiez le faire fonctionner je suppose :-)
humidee
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.