Почему HttpAntiForgeryException происходит случайным образом даже со статическим ключом машины?

У нас есть приложение ASP.NET MVC 2 (.NET 4), работающее на Windows Azure (последняя версия ОС 2.x) с двумя экземплярами веб-роли.

Мы используем токен анти-подделки, предоставленный MVC для всех запросов POST, и мы установили статический машинный ключ в web.config, поэтому все работает на нескольких машинах и через перезагрузки. 99,9% случаев он отлично работает.

Время от времени мы регистрируем исключение HttpAntiForgeryException с сообщением "Требуемый токен анти-подделки не был указан или был недействителен".

Я знаю, что проблема может заключаться в том, что cookie не разрешается в браузере, но мы проверили, что и файлы cookie включены и правильно отправлены туда и обратно.

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

Почему это происходит? Как мы можем избежать этого?

Ответы

Ответ 1

Я столкнулся с этим недавно и нашел две причины.

1. Браузер восстанавливает последний сеанс на открытой странице, которая кэшируется

Если у вас есть страница, которая является кэшируемой, которая выполняет сообщение на вашем сервере (т.е. антифригмент будет включен), и у пользователя установлен его браузер для восстановления последнего сеанса при запуске (эта опция существует в хроме), страница будет выведенный из кеша. Однако cookie проверки запроса не будет там, потому что это cookie сеанса браузера и отбрасывается, когда браузер закрыт. Поскольку cookie ушел, вы получаете исключение от подделки. Решение. Верните заголовки ответов, чтобы страница не была кеширована (т.е. Cache-Control: private, no-store).

2. Состояние гонки при открытии более одной вкладки при запуске на ваш сайт

Браузеры могут открывать набор вкладок при запуске. Если более одного из них попали на ваш сайт, который возвращает файл cookie проверки запроса, вы можете попасть в состояние гонки, в котором файл cookie проверки запроса перезаписан. Это происходит из-за того, что на ваш сервер попадает более одного запроса от пользователя, у которого нет набора cookie проверки запроса. Первый запрос обрабатывается и устанавливает файл cookie проверки запроса. Затем обрабатывается второй запрос, но он не отправляет файл cookie (еще не установлен во время запроса), поэтому сервер генерирует новый. Новый перезаписывает первый, и теперь эта страница будет получать исключение запроса на антикоррозионное обращение, когда она будет выполнять следующую запись. Структура MVC не обрабатывает этот сценарий. Эта ошибка была сообщена команде MVC в Microsoft.

Ответ 2

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

Вот как выглядит метод Validate:

public void Validate(HttpContextBase context, string salt)
{
    string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
    string str2 = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
    HttpCookie cookie = context.Request.Cookies[str2];
    if ((cookie == null) || string.IsNullOrEmpty(cookie.Value))
    {
        throw CreateValidationException();
    }
    AntiForgeryData data = this.Serializer.Deserialize(cookie.Value);
    string str3 = context.Request.Form[antiForgeryTokenName];
    if (string.IsNullOrEmpty(str3))
    {
        throw CreateValidationException();
    }
    AntiForgeryData data2 = this.Serializer.Deserialize(str3);
    if (!string.Equals(data.Value, data2.Value, StringComparison.Ordinal))
    {
        throw CreateValidationException();
    }
    string username = AntiForgeryData.GetUsername(context.User);
    if (!string.Equals(data2.Username, username, StringComparison.OrdinalIgnoreCase))
    {
        throw CreateValidationException();
    }
    if (!string.Equals(salt ?? string.Empty, data2.Salt, StringComparison.Ordinal))
    {
        throw CreateValidationException();
    }
}

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

Ответ 3

У меня есть несколько веб-приложений MVC3, которые также получают это довольно регулярно. Большинство из них связано с тем, что клиент не отправляет тело POST. И большинство из них - IE8 из-за некоторой ошибки с запросами ajax, предшествующими регулярному сообщению формы. Там исправление для IE, похоже, устраняет симптомы, что доказывает, что это ошибка клиента в этих случаях

http://support.microsoft.com/?kbid=831167

Есть несколько дискуссий о проблеме в Интернете, но ничего слишком полезного, хотя я определенно не собираюсь вмешиваться в тайм-ауты keep-alive, которые являются предлагаемым "решением" в некоторых местах...

https://www.google.com/search?q=ie8+empty+post+body

Я никогда не мог воспроизвести его с помощью различных попыток reset соединений между POSTS, поэтому я боюсь, что у меня нет реального решения для случая пустых POST-объектов IE. Мы немного смягчили это, чтобы убедиться, что мы никогда не используем метод POST при простое извлечение данных через ajax.

Если вы регистрируете полный запрос, проверьте, пуст ли POST-объект, и, если это так, вероятно, это более старый IE. И я не имею в виду Content-Length: 0, у него обычно будет Content-Length, который кажется правильным в заголовках, но буквально ничего не будет после заголовков в запросе.

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

Ответ 4

Есть несколько вариантов того, что вы могли бы попробовать. Вы можете попытаться удалиться в машину и посмотреть журнал событий, чтобы узнать, сможете ли вы получить дополнительную информацию о том, где это происходит. Если это не помогает, вы можете использовать DebugDiag или какой-либо другой инструмент для захвата дампа процесса (DebugDiag позволит вам зафиксировать его во время этого конкретного исключения). И затем посмотрите на это, чтобы посмотреть, что происходит.

Если вы не можете понять, что оттуда, вы всегда можете создать поддержку с Microsoft, чтобы помочь вам исследовать ее.

Ответ 5

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

Я пробовал все комбинации директив no-cache страниц, но иногда я все еще получаю кэшированные страницы.

Я обнаружил, что лучшим решением является привязка события onbeforeunload для страницы и явное очищение значения скрытого поля ввода, содержащего значение токена в DOM.

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

window.location.reload(true);

Кажется, что он работает довольно эффективно, и я подозреваю, что это может быть также для кода анти-подделки MVC.