Вы всегда ОТКЛЮЧАЕТСЯ после ПОСТ? Если да, как вам это удается?

Скажем, вы отправляете форму, которая влияет на вашу базу данных (добавляет записи/удаляет их/обновляет), и вот как выглядит ваш запрос:

POST/application/action = update

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

Response.sendRedirect/application/action = home

Это прекрасно работает. Пользователю отправляется перенаправление после POST, поэтому, даже если пользователь пытается обновить страницу, нажав F5, вы в порядке. Однако это не будет работать, если вы это сделаете:

RequestDispatcher.forward(/приложение/действие = дом)

Учитывая, что существует сценарий, в котором вы должны отображать различные сообщения об ошибках/успехе после того, как вы закончите с вашим обновлением, вы, скорее всего, выполняете пересылку после POST. В таком сценарии, как вы избегаете повторных действий по обновлению?

Я считаю довольно забавным, что многие защищенные сайты (банки)/платежные шлюзы, как правило, информируют пользователя, помещая текст на экран, например "Пожалуйста, не нажимайте кнопки" Назад/Обновить ".

Нет ли лучшего способа справиться с этим? Помимо запроса пользователю не нажимать эти кнопки? Когда я последний раз проверял, было что-то, называемое "Вертикальный ответный кэш". Фильтр, который идентифицирует уникальность вашего запроса в сеансе и пытается отправить кешированный ответ, если запрос дублируется. Существуют ли более простые способы решения этой классической проблемы?

Вот ссылка на решение кеша вертикального ответа, о котором я говорил: http://www.fingo.info/en/articles/_1.html. Я, однако, не уверен, насколько это действительно работает.

Ответы

Ответ 1

Одна мысль, что у меня была, - вставить уникальный идентификатор (вероятно, случайную строку) в качестве скрытого поля формы в форме, которая отправляется POST. Строка идентификатора может быть помещена в базу данных как "идентификатор транзакции". Теперь, когда вы переходите на обновление базы данных, сначала проверьте, существует ли существующая запись с представленным идентификатором транзакции, и если да, предположите, что это дубликат и не меняйте базу данных.

Конечно, как я уже сказал, это просто мысль. Я не знаю, какие методы фактически используются на практике. (Я подозреваю, что многие менее критичные сайты просто игнорируют проблему и надеются, что их пользователи будут умными... потерянное предложение, если я когда-нибудь увижу его; -)

EDIT: как указано в комментариях, сохранение идентификаторов транзакций в базе данных может занимать много места, но если это проблема, вы можете сохранить кеш в памяти всех идентификаторов транзакций, обработанных за последние 5 минут /1 час/1 день/независимо. Это должно работать, если вы не против определенного хакера...

Ответ 2

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

Response.sendRedirect работает на практике, но, по сути, это отправляет неверный код ответа HTTP для этой цели. sendRedirect отправляет 302, но правильный код для преобразования POST в GET равен 303. (большинство браузеров будут обрабатывать 302, как и 303, если они получат его в ответ на POST, однако)

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

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

/widget?id=12345&msg=Widget+modified.

Здесь параметр "msg" содержит сообщение "Виджет изменен". Единственным недостатком этого подхода является то, что злоумышленники могут сообщать вашим пользователям путать/вводить в заблуждение сообщения. например:

/account?msg=Foo+Corp.+hates+you.

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

Ответ 3

Лучшим решением для решения проблемы отображения сообщений о статусе пользователям после перенаправления POST для GET является использование пользовательских сеансов.

Как

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

userSession.put("success_messages", new HashSet<String>(){"Success", "Check your account balance"});
userSession.put("warning_messages", new HashSet<String>(){"Your account balance is low. Recharge immediately"});

И у вас есть фильтр, который сканирует сеанс пользователя для этих конкретных атрибутов и выводит сообщения. Фильтр должен удалить атрибуты после чтения один раз, поскольку сообщения о состоянии обычно отображаются только один раз.

Ответ 4

Я считаю довольно забавным, что многие защищенные сайты (банки)/платежные шлюзы, как правило, информируют пользователя, помещая текст на экран, например "Пожалуйста, не нажимайте кнопки" Назад/Обновить ".

некоторые люди считают, что лучше "отключить все Back, Refresh event на этих критических страницах"; Я не уверен, хорошо это или нет.

Но ваше адресованное решение " вертикальный кэш ответов" звучит хорошо

Ответ 5

Немного неочевидно, но:

  • создать ключевой объект в сеансе пользователя.
  • значение - это Request + java Future для результата
  • немедленно вернуться с перенаправлением на стороне клиента.
  • когда выполняется переадресация на стороне клиента, при работе с рабочим потоком необходимо выполнить ответ.

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

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

Обновление безопасности (2011 24 января):

Ключ уязвим для атаки, поскольку он является частью ответа клиенту, поэтому

  • Генерировать случайный ключ
  • Использовать идентификатор сеанса пользователя в качестве соли для создания SHA-1
  • Храните как случайный ключ, так и SHA-1 в базе данных с (,) в качестве первичного ключа. (без отдельной индексации на RANDOMKEY.
  • Используйте как RANDOMKEY, так и SHA-1 в качестве поиска db.
  • Не храните идентификатор сеанса (избегайте проблем с конфиденциальностью, чтобы иметь возможность связывать многие записи с одним и тем же пользователем).
  • Срок действия истекает через 2-3 дня. (Позволяет ежедневному пакетному заданию выполнять очистку и избегать создания проблем для пользовательских сеансов, которые являются полудолговечными).

Этот метод требует, чтобы любой хакер знал как идентификатор сеанса, так и случайный ключ.

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

Ответ 6

Если вы работаете со сценариями на стороне сервера Java, а также с помощью struts 2, вы ссылаетесь на эту ссылку, которая говорит об использовании токена.

http://www.xinotes.org/notes/note/369/

Маркер должен быть сгенерирован и сохранен в сеансе для первоначальной рендеринга страницы, когда запрос отправляется вместе с токеном в первый раз, в действии struts запускается поток с именем потока в качестве идентификатора маркера и выполняется логика клиент запросил, когда клиент снова отправит один и тот же запрос, проверьте, продолжает ли поток (thread.getcurrentthread() прерывается), если он еще запущен, а затем отправьте перенаправление клиента 503.

Посмотрите на ExecuteAndWaitInterceptor struts 2code, логика этого в сочетании с токеном поможет быстро щелкнуть