Токен защиты от подделки предназначен для пользователя «», но текущий пользователь - «имя пользователя».

130

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

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

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

  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набором свойств к истине , и все же до @Html.AntiForgeryTokenсих пор , кажется , чтобы сделать маркер для старого пользователя , если я не делаю перезагрузку страницы.

парламент
источник
Можете ли вы действительно убедиться, что код @ Html.AntiForgeryToken вызывается без перезагрузки?
Kyle C
Это определенно так, я могу успешно пройти там, чтобы проверить объект HttpContext.Current.User, как я уже упоминал
парламент
2
Пожалуйста, обратитесь к этому: stackoverflow.com/a/19471680/193634
Росди Касим
@par Parliament, расскажите, пожалуйста, какой вариант вы выбрали в ответе ниже.
Сиддхарт Пандей
Я считаю, что сделал исключение, чтобы выполнить полную перезагрузку, если я правильно помню. Но я ожидаю очень скоро столкнуться с этой проблемой в новом проекте. Отправлю ответ, если я выберу лучший рабочий вариант.
парламент

Ответы:

170

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

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

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

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

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

epignosisx
источник
21
@ парламент: вы приняли этот ответ, не могли бы вы поделиться с нами, какой вариант вы выбрали?
R. Schreurs
9
+1 за красивый и простой вариант 3. Временные выходы из системы поставщиками OAuth также вызывают эту проблему.
Gone Coding
18
Вариант 3 у меня не сработал. Выйдя из системы, я открыл два окна на странице входа. Вы вошли в систему как один пользователь в одном окне, затем вошли в систему как другой пользователь в другом и получили ту же ошибку.
McGaz
5
К сожалению, мне не удалось найти хорошего решения этой проблемы. Я удалил токен со страницы входа. Я все еще включаю его в сообщения после входа в систему.
McGaz
7
Вариант 3 у меня тоже не сработал. По-прежнему возникает такая же ошибка.
Жоао Леме,
25

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

[OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")] 
public ActionResult Login(string returnUrl)
user3401354
источник
3
Это решило проблему для меня, имеет смысл. Спасибо!
Prime03,
В моем случае пользователь попытался войти в систему и получил сообщение об ошибке, например «учетная запись отключена» через ModelState.AddError (). Затем, если они снова нажмут «Войти», они увидят эту ошибку. Однако это исправление снова дало им новое пустое представление входа в систему, а не ошибку токена защиты от подделки. Итак, это не исправление.
yourpublicdisplayname
Мой случай: 1. Пользователь входит в систему () и переходит на домашнюю страницу. 2. Пользователь нажимает кнопку «Назад» и возвращается в режим входа в систему. 3. Пользователь снова войдет в систему и увидит ошибку «Токен защиты от подделки предназначен для пользователя», но текущий пользователь - «имя пользователя» ». На странице ошибки. Если пользователь щелкает любые другие вкладки в меню, приложение работает должным образом. , Используя приведенный выше код, пользователь все равно может нажать кнопку «Назад», но он будет перенаправлен на домашнюю страницу. Таким образом, независимо от того, сколько раз пользователь нажимает кнопку «Назад», он перенаправляет его на домашнюю страницу. Спасибо
Рави
Есть идеи, почему это не работает в веб-просмотре Xamarin?
Noobie3001
1
Полное объяснение см. В разделе «
Повышение
15

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

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

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

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

Рикардо Франса
источник
Это была моя проблема. Спасибо!
Nanou Ponette
8

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

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

...

мнемоника
источник
2
Похоже, у меня была такая же проблема. ИМО, это не взлом, это обычная вещь, которую мы все должны проверять при входе в систему. Если пользователь уже вошел в систему, просто выйдите из него и отобразите страницу входа. Исправил мою проблему, спасибо.
Александр
7

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

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

System.Web.Helpers.AntiForgery.Validate()

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

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

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

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

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

А. Морель
источник
Отличное решение! Это решило мою проблему после того, как я попробовал много других предложений, которые не сработали. Во-первых, мне было сложно воспроизводить ошибку, пока я не обнаружил, что это могло быть из-за того, что 2 браузера или вкладки открывались с одной и той же страницей, и пользователь входил в систему с одного, а затем входил со второго без перезагрузки.
Ники
Спасибо за это решение. У меня тоже сработало. Я добавил проверку, чтобы увидеть, совпадает ли Identity с именем пользователя для входа, и если да, я с радостью продолжаю попытки входа пользователя в систему и выходить из системы, если это не так. Например, попробуйте {System.Web.Helpers.AntiForgery.Validate ();} catch (HttpAntiForgeryException) {if (! User.Identity.IsAuthenticated || string.Compare (User.Identity.Name, model.Username)! = 0) {// Здесь ваша логика выхода}}
Стив Оуэн,
2

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

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

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

Как решить?

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

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
Бриджеш Мавани
источник
1

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

    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

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

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

Liam
источник
1
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")]

public ActionResult Login(string returnUrl)

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

Мариан Далалау
источник
0

У меня была такая же проблема с одностраничным приложением 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)
    {
        //...
    }
}
Нед Хоули
источник
-3

Имеет проблему с проверкой токенов защиты от подделки в интернет-магазине: пользователи открывают множество вкладок (с товарами) и после входа в одну пытаются авторизоваться в другой и получают такое 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());
    }
}

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

user3364244
источник
12
Это ужасный пример решения проблемы, о которой идет речь. Не используйте это.
xxbbcc
Полностью согласен с xxbbcc.
Хавьер
Хорошо, вариант использования: форма входа с токеном защиты от подделки. Откройте его в 2-х вкладках браузера. Авторизуйтесь первым. Вы не можете обновить вторую вкладку. Какое решение вы предлагаете для правильного поведения пользователя, который пытается войти в систему со второй вкладки?
user3364244 06
@ user3364244: правильное поведение может быть следующим: обнаружение внешнего входа в систему с помощью веб-сокетов или signalR. Это та же самая сессия, так что вы могли бы заставить ее работать, я думаю :-)
dampee