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

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

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

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

  1. Пользователь, не вошедший в систему, загружает диалоговое окно (с созданным токеном против подделки)
  2. Пользователь закрывает диалог
  3. Пользователь входит в систему
  4. Пользователь открывает тот же диалог
  5. Пользователь отправляет форму в диалоге

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

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

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

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

Как я могу изменить @Html.AntiForgeryToken() вернуть токен для нового аутентифицированного пользователя?

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

10 ответов

Решение

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

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

  1. Только в этот раз пусть ваш SPA сделает полный POST, и когда страница перезагрузится, у него будет токен защиты от подделки со встроенным обновленным именем пользователя.

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

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

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

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

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

Этот Помощник делает то же самое, что и [ValidateAntiForgeryToken] приписывать.

System.Web.Helpers.AntiForgery.Validate()

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

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

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

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

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

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

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

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

Я надеюсь, что это помогает! знак равно

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

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

...

У меня такое же исключение происходит большую часть времени на производственном сервере.

Почему это происходит?

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

Как решить?

Просто добавьте эту строку и работайте без ошибок.

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

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

    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 ("AccountDetails") давало мне исключение токена, я полагаю, потому что функция ConfirmEmail была украшена AllowAnonymous, но функция AccountDetails имела ValidateAntiForgeryToken.

Изменение Return to Return RedirectToAction("AccountDetails") решило проблему для меня.

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

public ActionResult Login(string returnUrl)

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

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

Контроллер (NB. Я управляю пользователями с помощью 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" });
    }
}

Фильтр результатов для добавления файлов cookie для защиты от подделки:

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

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

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

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

Другие вопросы по тегам