Il token anti-contraffazione è pensato per l'utente "" ma l'utente corrente è "nome utente"


130

Sto creando un'applicazione a pagina singola e sto riscontrando un problema con i token anti-contraffazione.

So perché il problema si verifica, ma non so come risolverlo.

Viene visualizzato l'errore quando si verifica quanto segue:

  1. L'utente non connesso carica una finestra di dialogo (con un token anti-contraffazione generato)
  2. L'utente chiude la finestra di dialogo
  3. L'utente accede
  4. L'utente apre la stessa finestra di dialogo
  5. L'utente invia il modulo nella finestra di dialogo

Il token anti-contraffazione è pensato per l'utente "" ma l'utente corrente è "nome utente"

Il motivo per cui ciò accade è perché la mia applicazione è al 100% a pagina singola e quando un utente accede correttamente tramite un post Ajax /Account/JsonLogin, cambio semplicemente le viste correnti con le "viste autenticate" restituite dal server ma non ricaricare il pagina.

So che questo è il motivo perché se ricarico semplicemente la pagina tra i passaggi 3 e 4, non si verifica alcun errore.

Quindi sembra che @Html.AntiForgeryToken()nel modulo caricato restituisca ancora un token per il vecchio utente fino a quando la pagina non viene ricaricata.

Come posso modificare @Html.AntiForgeryToken()per restituire un token per il nuovo utente autenticato?

Inietto un nuovo GenericalPrincipalcon un'abitudine IIdentitysu ogni Application_AuthenticateRequestcosì quando @Html.AntiForgeryToken()viene chiamato HttpContext.Current.User.Identity, in effetti la mia identità personalizzata con IsAuthenticatedproprietà impostata su true e tuttavia @Html.AntiForgeryTokensembra ancora rendere un token per il vecchio utente a meno che non esegua un ricaricamento della pagina.


Puoi effettivamente verificare che il codice @ Html.AntiForgeryToken venga chiamato senza ricaricare?
Kyle C

Sicuramente, posso fare con successo una pausa per ispezionare l'oggetto HttpContext.Current.User come ho già detto
parlamento

2
Si prega di fare riferimento a questo: stackoverflow.com/a/19471680/193634
Rosdi Kasim

@Per favore, potresti dire quale opzione hai scelto nella risposta qui sotto.
Siddharth Pandey,

Credo di aver fatto un'eccezione per andare con una ricarica completa se ricordo bene. Ma mi aspetto di incontrare questo problema molto presto in un nuovo progetto. Spedirò indietro se scelgo con una migliore opzione di lavoro.
parlamento

Risposte:


170

Ciò accade perché il token anti-contraffazione incorpora il nome utente dell'utente come parte del token crittografato per una migliore convalida. Quando si chiama per la prima volta @Html.AntiForgeryToken()l'utente non ha effettuato l'accesso, quindi il token avrà una stringa vuota per il nome utente, dopo che l'utente ha effettuato l'accesso, se non si sostituisce il token anti-contraffazione, non passerà la convalida perché il token iniziale era per utente anonimo e ora abbiamo un utente autenticato con un nome utente noto.

Hai alcune opzioni per risolvere questo problema:

  1. Proprio questa volta lascia che la tua SPA esegua un POST completo e quando la pagina viene ricaricata avrà un token anti-contraffazione con il nome utente aggiornato incorporato.

  2. Avere una visione parziale con giusto @Html.AntiForgeryToken()e subito dopo l'accesso, eseguire un'altra richiesta AJAX e sostituire il token anti-contraffazione esistente con la risposta della richiesta.

  3. Basta disabilitare il controllo dell'identità eseguito dalla convalida anti-contraffazione. Aggiungere la seguente al Application_Start metodo: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.


21
@Parlamento: hai accettato questa risposta, potresti condividere con noi quale opzione hai scelto?
R. Schreurs,

9
+1 per l'opzione semplice e piacevole 3. Anche il logout temporizzato da parte dei provider OAuth causa questo problema.
Codice finito

18
L'opzione 3 non ha funzionato per me. Durante la disconnessione, ho aperto due finestre nella pagina di accesso. Accesso effettuato come un utente in una finestra, quindi accesso come un altro utente nell'altro e ricevuto lo stesso errore.
McGaz,

5
Sfortunatamente, non sono riuscito a trovare una buona soluzione a questo. Ho rimosso il token dalla pagina di accesso. Lo includo ancora nei post dopo il login.
McGaz,

7
L'opzione 3 non ha funzionato neanche per me. Sempre visualizzato lo stesso errore.
Joao Leme,

25

Per correggere l'errore è necessario posizionare l' OutputCacheannotazione dei dati nella ActionResultpagina Ottieni l' accesso come:

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

3
Questo ha risolto il problema per me, ha perfettamente senso. Grazie!
Prime03

Il mio caso d'uso è stato l'utente ha tentato un accesso e mi è stato mostrato un errore, ad esempio "account disabilitato" tramite ModelState.AddError (). Quindi se facessero nuovamente clic su login vedrebbero questo errore. Tuttavia, questa correzione ha appena restituito loro una nuova vista di accesso vuota anziché l'errore del token anti-contraffazione. Quindi, non una soluzione.
yourpublicdisplayname

Il mio caso: 1. Accesso utente () e atterra nella home page. 2. L'utente preme il pulsante Indietro e torna alla vista Accesso. 3. L'utente effettua nuovamente l'accesso e visualizza l'errore "Il token anti-contraffazione è pensato per l'utente" "ma l'utente corrente è" nome utente "" Nella pagina di errore Se l'utente fa clic su qualsiasi altra scheda dal menu, l'applicazione funzionava come previsto . Utilizzando il codice sopra l'utente può ancora premere il pulsante Indietro ma viene reindirizzato alla home page. Quindi, indipendentemente da quante volte l'utente preme il pulsante Indietro, lo reindirizzerà alla home page. Grazie
Ravi,

Qualche idea sul perché questo non funziona su una webview Xamarin?
Noobie3001,

1
Per una spiegazione completa, vedere il miglioramento delle prestazioni con la cache di output
stomy


8

Ho avuto lo stesso problema e questo trucco sporco l'ha risolto, almeno fino a quando non riesco a risolverlo in modo più pulito.

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

...


1
Sembra che abbia avuto lo stesso problema. IMO non è un trucco, è più una cosa comune che tutti dovremmo verificare quando effettuano l'accesso. Se l'utente ha già effettuato l'accesso, basta disconnettersi e visualizzare la pagina di accesso. Risolto il mio problema, grazie.
Alexandre,

7

Il messaggio appare quando accedi quando sei già autenticato.

Questo aiutante fa esattamente la stessa cosa [ValidateAntiForgeryToken]dell'attributo.

System.Web.Helpers.AntiForgery.Validate()

Rimuovere l' [ValidateAntiForgeryToken]attributo dal controller e mettere questo helper in modalità di azione.

Pertanto, quando l'utente è già autenticato, reindirizzare alla home page o, in caso contrario, continuare con la verifica del token anti-contraffazione valido dopo questa verifica.

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

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

Per provare a riprodurre l'errore, procedere come segue: Se ci si trova nella pagina di accesso e non si è autenticati. Se si duplica la scheda e si accede con la seconda scheda. E se torni alla prima scheda nella pagina di accesso e provi ad accedere senza ricaricare la pagina ... hai questo errore.


Ottima soluzione! Ciò ha risolto il mio problema dopo aver provato molti altri suggerimenti che non funzionavano. Innanzitutto è stato un dolore riprodurre l'errore, fino a quando non ho scoperto che poteva essere dovuto a 2 browser o schede aperte con la stessa pagina e all'utente che accedeva da uno e quindi accedeva dal secondo senza ricaricare.
Nicki,

Grazie per questa soluzione Ha funzionato anche per me. Ho aggiunto un segno di spunta per verificare se Identity era uguale al nome utente di accesso e, in tal caso, continuo a tentare felicemente di accedere all'utente e di disconnetterlo in caso contrario. Ad esempio, prova {System.Web.Helpers.AntiForgery.Validate ();} catch (HttpAntiForgeryException) {if (! User.Identity.IsAuthenticated || string.Compare (User.Identity.Name, model.Username)! = 0) {// La tua logica di disconnessione qui}}
Steve Owen il

2

Ho la stessa eccezione che si verifica la maggior parte del tempo sul server di produzione.

Perché succede?

Succede quando l'utente accede con credenziali valide e una volta effettuato l'accesso e reindirizza a un'altra pagina, e dopo aver premuto il pulsante Indietro mostrerà la pagina di accesso e di nuovo inserirà credenziali valide nel momento in cui si verificherà questa eccezione.

Come risolvere?

Basta aggiungere questa riga e lavorare perfettamente, senza ottenere un errore.

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

1

Ho avuto un problema abbastanza specifico ma simile nel processo di registrazione. Una volta che l'utente ha fatto clic sul collegamento e-mail inviato, sarebbe stato effettuato l'accesso e inviato direttamente alla schermata dei dettagli dell'account per inserire ulteriori informazioni. Il mio codice 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

Ho scoperto che la vista di ritorno ("AccountDetails") mi stava dando l'eccezione token, immagino perché la funzione ConfirmEmail era decorata con AllowAnonymous ma la funzione AccountDetails aveva ValidateAntiForgeryToken.

La modifica del ritorno al ritorno RedirectToAction ("AccountDetails") ha risolto il problema per me.


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

public ActionResult Login(string returnUrl)

È possibile verificarlo inserendo un punto di interruzione nella prima riga dell'azione di accesso (Ottieni). Prima di aggiungere la direttiva OutputCache il breakpoint verrebbe colpito al primo caricamento, ma dopo aver fatto clic sul pulsante Indietro del browser non lo avrebbe fatto. Dopo aver aggiunto la direttiva dovresti finire col colpire ogni volta il punto di interruzione, quindi AntiForgeryToken sarà quello corretto, non quello vuoto.


0

Ho avuto lo stesso problema con un'applicazione ASP.NET MVC Core a pagina singola. L'ho risolto impostando HttpContext.Userin tutte le azioni del controller che cambiano le attuali rivendicazioni di identità (poiché MVC lo fa solo per le richieste successive, come discusso qui ). Ho usato un filtro dei risultati anziché il middleware per aggiungere i cookie antiforgery alle mie risposte, il che mi ha assicurato che fossero generati solo dopo che l'azione MVC era tornata.

Controller (NB. Sto gestendo gli utenti 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 risultati per aggiungere cookie 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)
    {

    }
}

Estratto 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

Ha un problema con la convalida del token anti-contraffazione nell'internet-shop: gli utenti aprono molte schede (con merce) e dopo aver effettuato l'accesso in uno provano ad accedere ad un altro e ottengono tale AntiForgeryException. Quindi, AntiForgeryConfig.SuppressIdentityHeuristicChecks = true non mi ha aiutato, quindi ho usato un così brutto hackfix, forse sarà utile per qualcuno:

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

Pensa che sarebbe fantastico se si possono impostare opzioni di generazione di token anti-contraffazione, per escludere il nome utente o qualcosa del genere.


12
Questo è un terribile esempio di come affrontare il problema nella domanda. Non usare questo.
xxbbcc

Totalmente d'accordo con xxbbcc.
Javier

OK, utilizzare il caso: modulo di accesso con token anti-contraffazione. Aprilo in 2 schede del browser. Accedi prima. Non è possibile aggiornare la seconda scheda. Quale soluzione suggerisci di avere un comportamento corretto per l'utente che tenta di accedere dalla seconda scheda?
user3364244

@ user3364244: il comportamento corretto può essere il seguente: rilevare un accesso esterno utilizzando websocket o signalR. Questa è la stessa sessione in modo che tu possa farlo funzionare credo :-)
dampee
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.