Маркер проти підробки призначений для користувача "", але поточний користувач - "ім'я користувача"


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 викликається без перезавантаження?
Кайл С

Безумовно, я можу успішно перерватися там, щоб оглянути HttpContext.Current.User об'єкт, як я вже згадував
парламент

2
Будь ласка , зверніться до цього: stackoverflow.com/a/19471680/193634
Rosdi Касим

@par Parlament, будь ласка, скажіть, до якого варіанту ви звернулися у відповіді нижче.
Сіддхарт Панді

Я вважаю, що я зробив виняток, щоб піти з повним перезавантаженням, якщо я правильно пам’ятаю. Але я очікую, що незабаром у новому проекті зіткнуться з цим питанням. Відправить повідомлення, якщо я виберу кращий робочий варіант.
парламент

Відповіді:


170

Це відбувається тому, що маркер проти підробки вбудовує ім’я користувача як частину зашифрованого маркера для кращої перевірки. Коли ви вперше зателефонуєте, @Html.AntiForgeryToken()користувач не входить у систему, тож маркер буде мати порожню рядок для імені користувача, після входу користувача, якщо ви не заміните маркер анти-підробки, він не пройде перевірку, оскільки початковий маркер був для анонімний користувач, і тепер у нас є автентифікований користувач із відомим ім'ям користувача.

У вас є кілька варіантів вирішення цієї проблеми:

  1. Якраз цього разу дозвольте вашому SPA зробити повний POST, і коли сторінка перезавантажиться, він матиме маркер проти підробки з вбудованим оновленим іменем користувача.

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

  3. Просто вимкніть перевірку особи, яка виконує перевірку підробки. Додайте наступний рядок в ваш Application_Start метод: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.


21
@par Парламент: ви прийняли цю відповідь, чи можете ви поділитися з нами, який варіант ви обрали?
Р. Шреурс

9
+1 для приємного та простого варіанту 3. Визначені тимчасові виходи від постачальників OAuth також спричиняють цю проблему.
Пройшов кодування

18
Варіант 3 не працював для мене. Під час виходу з системи я відкрив два вікна на сторінку входу. Увійшли як один користувач в одному вікні, потім увійшли як інший користувач в іншому і отримали ту саму помилку.
МакГаз

5
На жаль, я не зміг знайти хорошого рішення цього питання. Я видалив маркер зі сторінки входу. Я все ще включаю його в повідомлення після входу.
Макгаз

7
Варіант 3 не працював і для мене. Все ж отримує таку ж помилку.
Жоао Леме

25

Щоб виправити помилку, потрібно розмістити OutputCacheанотацію даних на сторінці ActionResult"Вхід у систему" як:

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

3
Це вирішило для мене питання, має повний сенс. Дякую!
Prime03

У моєму випадку використання було те, що користувач спробував увійти в систему, і йому з’явилася помилка, наприклад, "рахунок вимкнено" через ModelState.AddError (). Потім, якщо вони знову натиснули логін, вони побачать цю помилку. Однак це виправлення просто повернуло їм порожній свіжий вхід знову, а не помилку лексеми проти підробки. Отже, не виправлення.
вашепублічне відображення імені,

Мій випадок: 1. Вхід користувача () та розміщення на головній сторінці. 2. Користувач натискає кнопку назад і повертається до перегляду Вхід. 3. Користувач знову увійдіть і побачите помилку "Маркер антифрежіння призначений для користувача" ", але поточний користувач -" ім'я користувача "" На сторінці помилки Якщо користувач натискає будь-які інші вкладки з меню, програма працювала як очікувалося . Користуючись вищевказаним кодом, користувач все ще може натиснути кнопку назад, але він буде переспрямований на головну сторінку. Тож незалежно від того, скільки разів користувач натискає кнопку "назад", він перенаправить його на головну сторінку. Дякую
Раві

Будь-які ідеї, чому це не працює на веб-перегляді Xamarin?
Noobie3001


15

З моєю заявою це трапляється багато разів, тому я вирішив погуглювати її!

Я знайшов просте пояснення щодо цієї помилки! Користувач двічі клацає кнопку для входу! Ви можете бачити іншого користувача, що говорить про це, за посиланням нижче:

MVC 4 надав маркер проти підробки, призначений для користувача, але поточний користувач - "користувач"

Я сподіваюся, що це допомагає! =)


Це була моя проблема. Дякую!
Nanou Ponette

8

У мене була така ж проблема, і цей брудний злом усунув її, принаймні, поки я не зможу виправити це більш чистим способом.

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

...


1
Схоже, у мене була така ж проблема. IMO це не хак, це більше звичайна річ, яку ми всі повинні перевірити під час входу. Якщо користувач уже ввійшов у систему, просто вийдіть із нього та покажіть сторінку входу. Виправили мою проблему, дякую.
Олександр

7

Повідомлення з’являється під час входу в систему, коли ви вже зареєстровані.

Цей Помічник робить точно те саме, що і [ValidateAntiForgeryToken]атрибут.

System.Web.Helpers.AntiForgery.Validate()

Видаліть [ValidateAntiForgeryToken] аттрибут з контролера і помістіть цього помічника в метод дії.

Тож, коли користувач уже зареєстрований, перенаправляйте на домашню сторінку або якщо не продовжуєте перевірку дійсного маркера проти підробки після цієї перевірки.

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

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

Щоб спробувати відтворити помилку, поступайте так: Якщо ви перебуваєте на своїй сторінці входу, і ви не маєте автентифікацію. Якщо ви дублюєте вкладку і ви входите з другою вкладкою. Якщо ви повернетесь до першої вкладки на сторінці входу і спробуєте увійти, не перезавантажуючи сторінку ... у вас є ця помилка.


Відмінне рішення! Це вирішило моє питання, спробувавши багато інших пропозицій, які не спрацювали. По-перше, це біль, що відтворює помилку, поки я не виявив, що це може бути через те, що два браузери або вкладки відкриваються з тією ж сторінкою, і користувач увійшов з однієї сторінки, а потім увійшов з другої без перезавантаження.
Ніккі

Дякую за це рішення. Працював і для мене. Я додав чек, щоб побачити, чи ідентичність збігається з іменем для входу, і якщо так, я із задоволенням продовжую намагатися ввійти до користувача, і вийдіть, якщо це не так. Наприклад, спробуйте {System.Web.Helpers.AntiForgery.Validate ();} ловити (HttpAntiForgeryException) {if (! User.Identity.IsAuthentication || 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.

Змінення повернення на повернення RedirectToAction ("AccountDetails") вирішило для мене проблему.


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

public ActionResult Login(string returnUrl)

Ви можете перевірити це, поставивши крапку в першому рядку дії Вхід (Отримати). Перш ніж додати директиву OutputCache, точка розриву буде натиснута при першому завантаженні, але після натискання кнопки повернення браузера вона не стане. Після додавання директиви, ви повинні в кінцевому підсумку кожного разу потрапляти на точку розриву, тож AntiForgeryToken буде основним, а не порожнім.


0

У мене була та сама проблема з односторінковою програмою ASP.NET MVC Core. Я вирішив це, встановившиHttpContext.User всі дії контролера, які змінюють поточні претензії на ідентифікацію (оскільки MVC робить це лише для наступних запитів, про що йдеться тут ). Я застосував фільтр результатів замість проміжного програмного забезпечення, щоб додати відповідні файли cookie до моїх відповідей, які гарантували, що вони були створені лише після повернення дії 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:

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

Подумайте, що буде чудово, якщо можна встановити параметри генерації анти-підробки, щоб виключити ім’я користувача або щось подібне.


12
Це жахливий приклад вирішення проблеми у питанні. Не використовуйте це.
xxbbcc

Повністю згоден з xxbbcc.
Хав'єр

Гаразд, використовуйте регістр: форма для входу з маркуванням проти підробки. Відкрийте його на 2 вкладках браузера. Увійдіть спочатку. Ви не можете оновити другу вкладку. Яке рішення ви пропонуєте мати правильну поведінку для користувачів, які намагаються увійти з другої вкладки?
користувач3364244

@ user3364244: правильна поведінка може наступне: виявити зовнішній логін за допомогою веб-розетки або signalR. Це той самий сеанс, щоб ви могли змусити його працювати: я думаю :-)
dampee
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.