Анти-поддельный токен предназначен для пользователя ", но текущий пользователь -" имя пользователя"

Я создаю одностраничное приложение и испытываю проблему с токенами подделки.

Я знаю, почему проблема возникает, я просто не знаю, как ее исправить.

Я получаю ошибку, когда происходит следующее:

  • Пользователь без входа загружает диалоговое окно (с генерируемым токеном анти-подделки)
  • Пользователь закрывает диалог
  • Пользователь регистрируется в
  • Пользователь открывает тот же диалог
  • Пользователь отправляет форму в диалоговом окне

токен анти-подделки предназначен для пользователя ", но текущий пользователь" Имя пользователя"

Причина этого в том, что мое приложение на 100% одностраничное, и когда пользователь успешно входит в систему через пост ajax до /Account/JsonLogin, я просто отключу текущие представления с помощью "аутентифицированных представлений", возвращенных из сервер, но не перезагружать страницу.

Я знаю, что это причина, потому что, если я просто перезагружу страницу между шагами 3 и 4, ошибки не будет.

Итак, кажется, что @Html.AntiForgeryToken() в загруженной форме по-прежнему возвращает токен для старого пользователя, пока страница не будет перезагружена.

Как изменить @Html.AntiForgeryToken(), чтобы вернуть токен для нового, прошедшего проверку подлинности пользователя?

Я добавляю новый GenericalPrincipal с пользовательским IIdentity на каждом Application_AuthenticateRequest, поэтому к моменту @Html.AntiForgeryToken() получает вызов HttpContext.Current.User.Identity, на самом деле мой пользовательский Identity с свойством IsAuthenticated, установленным в true и все же @Html.AntiForgeryToken по-прежнему представляется маркером для старого пользователя, если я не перезаряжаю страницу.

Ответ 1

Это происходит потому, что токен анти-подделки вставляет имя пользователя пользователя как часть зашифрованного токена для лучшей проверки. Когда вы сначала вызываете @Html.AntiForgeryToken(), пользователь не регистрируется, поэтому у токена будет пустая строка для имени пользователя, после входа пользователя в систему, если вы не замените токен анти-подделки, он не пройдет проверку, потому что начальная токен был анонимным пользователем, и теперь у нас есть аутентифицированный пользователь с известным именем пользователя.

У вас есть несколько вариантов решения этой проблемы:

  • На этот раз пусть ваш СПА сделает полноценный POST, а когда перезагрузится страница, у него будет токен анти-подделки с обновленным именем пользователя.

  • Сделайте частичное представление только с @Html.AntiForgeryToken() и сразу после входа в систему выполните еще один запрос AJAX и замените существующий токен анти-подделки ответом на запрос.

  • Просто отключите проверку подлинности, проверяя проверку анти-подделки. Добавьте в свой метод Application_Start следующее: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.

Ответ 2

Чтобы исправить ошибку, вам нужно разместить OutputCache Аннотацию данных на странице Get ActionResult на странице входа:

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

Ответ 3

Это случается много раз с моим приложением, поэтому я решил сделать это для Google!

Я нашел простое объяснение этой ошибки! Пользователь дважды нажимает кнопку для входа в систему! Вы можете видеть, как другой пользователь говорит об этом по ссылке ниже:

MVC 4 предоставил токен анти-подделки для пользователя "" но текущий пользователь является" пользователем"

Надеюсь, это поможет! =)

Ответ 4

У меня была та же проблема, и этот грязный хак исправил ее, по крайней мере, до тех пор, пока я не починю ее чище.

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

...

Ответ 5

У меня была довольно конкретная, но схожая проблема в процессе регистрации. Как только пользователь нажал на ссылку электронной почты, отправленную им, они будут зарегистрированы и отправлены прямо на экран сведений о счете, чтобы заполнить дополнительную информацию. Мой код:

    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

Я обнаружил, что Return View ( "AccountDetails" ) давал мне исключение для токенов, я предполагаю, потому что функция ConfirmEmail была украшена AllowAnonymous, но функция AccountDetails имела ValidateAntiForgeryToken.

Изменение возврата к возврату RedirectToAction ( "AccountDetails" ) решило проблему для меня.

Ответ 6

У меня была такая же проблема с одностраничным приложением ASP.NET MVC Core. Я разрешил его, установив HttpContext.User во все действия контроллера, которые изменяют текущие утверждения идентичности (поскольку MVC делает это только для последующих запросов, как обсуждалось здесь). Я использовал фильтр результатов вместо промежуточного программного обеспечения, чтобы добавить файлы cookie antiforgery в мои ответы, которые гарантировали, что они были созданы только после того, как действие MVC вернулось.

Контроллер (NB. Я управляю пользователями с идентификатором ядра ASP.NET):

[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" });
    }
}

Фильтр результатов, чтобы добавить файлы 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)
    {

    }
}

Вывод 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)
    {
        //...
    }
}

Ответ 7

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

public ActionResult Login(string returnUrl)

Вы можете проверить это, поставив точку прерывания в первую строку вашего действия Login (Get). Прежде чем добавить директиву OutputCache, точка останова будет ударяться по первому загружаемому файлу, но после нажатия кнопки возврата браузера не будет. После добавления этой директивы вы должны в конечном итоге поражать точку останова, поэтому AntiForgeryToken будет corect one, а не пустой.

Ответ 8

Сообщение появляется при входе в систему, когда вы уже прошли аутентификацию.

Этот помощник выполняет то же самое, что и атрибут [ValidateAntiForgeryToken].

System.Web.Helpers.AntiForgery.Validate()

Удалите атрибут [ValidateAntiForgeryToken] от контроллера и поместите этот помощник в методе действия.

Итак, когда пользователь уже аутентифицирован, перенаправляйтесь на домашнюю страницу или не продолжайте проверку действительного токена анти-подделки после этой проверки.

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

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

Чтобы попытаться воспроизвести ошибку, действуйте следующим образом: Если вы находитесь на своей странице входа, и вы не авторизованы. Если вы дублируете вкладку и вы входите в систему со второй вкладкой. И если вы вернетесь к первой вкладке на странице входа и попытаетесь войти в систему, не перезагружая страницу... у вас есть эта ошибка.

Ответ 9

Проблема с проверкой анти-подделки в интернет-магазине: пользователи открывают много вкладок (с товарами) и после входа в систему пытаются попытаться войти в систему на другом и получить такое AntiForgeryException. Итак, AntiForgeryConfig.SuppressIdentityHeuristicChecks = true не помог мне, поэтому я использовал такой уродливый hackfix, может быть, это будет полезно для кого-то:

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

Подумайте, будет здорово, если будут установлены опции генерации анти-подделки-маркера, чтобы исключить имя пользователя или что-то в этом роде.