Как я могу предотвратить повторную запись в базу данных, когда браузер выполняет перезагрузку / возврат?

Я собираю небольшое веб-приложение, которое записывает в базу данных (Perl CGI и MySQL). Сценарий CGI берет некоторую информацию из формы и записывает ее в базу данных. Однако я заметил, что если я нажму «Перезагрузить» или «Назад» в веб-браузере, он снова запишет данные в базу данных. Я не хочу этого.

Как лучше всего защититься от перезаписи данных в этом случае?


person Marty    schedule 20.11.2008    source источник


Ответы (7)


Не используйте запросы GET для внесения изменений! Будьте RESTful; используйте POST (или PUT) вместо этого браузер должен предупредить пользователя, чтобы он не перезагружал запрос. Перенаправление (с помощью перенаправления HTTP) на страницу квитанции с использованием обычного запроса GET после POST / Запрос PUT позволит обновить страницу без предупреждения о повторной отправке.

РЕДАКТИРОВАТЬ:

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

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

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

person Stein G. Strindhaug    schedule 20.11.2008
comment
ОП жалуется на то, что его пользователи испортили систему. Сказать, чтобы их браузер попытался остановить их, вряд ли можно считать чугунным решением проблемы. - person Sam Kington; 22.11.2008
comment
Нет, но это снижает вероятность того, что добросовестные пользователи сделают это, я добавил несколько идей для дальнейшего уменьшения проблемы. - person Stein G. Strindhaug; 22.11.2008

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

person converter42    schedule 20.11.2008

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

Только мои 2 цента.

person Allain Lalonde    schedule 20.11.2008

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

person rjray    schedule 21.11.2008
comment
Вы выдаете номер билета, а затем ударяете по нему только один раз. - person dkretz; 21.11.2008

Во-первых, вы не можете доверять браузеру, поэтому любые разговоры об использовании POST, а не GET, в основном бесполезны. Да, клиент может получить предупреждение типа «Вы хотели повторно отправить эти данные?», Но вполне возможно, что он скажет «Да, теперь оставь меня в покое, глупый компьютер».

И это правильно: если вам не нужны дублирующиеся материалы, решать вам, а не пользователя.

Вы, вероятно, имеете некоторое представление о том, что значит быть дублирующимся представлением. Может быть, это тот же IP-адрес в течение нескольких секунд, может быть, это тот же заголовок сообщения в блоге или URL-адрес, который был недавно отправлен. Возможно, это комбинация ценностей - например, IP-адрес, адрес электронной почты и заголовок темы для отправки контактной формы. В любом случае, если вы вручную обнаружили несколько дубликатов в своих данных, вы сможете найти способ программной идентификации дубликата во время отправки и либо пометить его для утверждения вручную (если вы не уверены), или просто сказать отправителю: «Вы дважды щелкнули?» (Если информация не является на удивление конфиденциальной, вы можете представить имеющуюся у вас запись для них и сказать: «Это то, что вы хотели нам отправить? Если да, то вы уже это сделали - ура»)

person Sam Kington    schedule 22.11.2008

Я бы не стал полагаться на предупреждения POST от браузера. Пользователи просто нажимают ОК, чтобы сообщения исчезли.

В любое время у вас будет запрос, который должен быть только один раз, например, «произвести платеж», отправить уникальный токен, который будет отправлен обратно вместе с запросом. Выбросьте токен после того, как он вернется, и теперь вы можете определить, когда что-то является действительным представлением (что-либо с токеном, который не является «активным»). Срок действия активных токенов истекает через X промежутков времени, например когда пользовательский сеанс заканчивается.

(поочередно отслеживайте вернувшиеся жетоны, и если вы получили их раньше, то они недействительны.)

person quick_dry    schedule 22.11.2008
comment
Однако все еще есть проблема с условиями гонки. Как только вы получите токен, вам необходимо заранее убедиться, что он не использовался, например выполните команду обновления SQL и проверьте, равно ли возвращаемое значение 1 (вы что-то изменили) или 0E0 (вы пытались изменить его на то, что уже было). - person Sam Kington; 24.11.2008

Выполняйте POST каждый раз, когда вы изменяете данные, но никогда не возвращаете HTML-ответ от сообщения ... вместо этого возвращайте перенаправление на GET, который извлекает обновленные данные в качестве страницы подтверждения. Таким образом, можно не беспокоиться о том, что они обновят страницу. Если они обновятся, все, что произойдет, - это еще одно извлечение, а не действие по изменению данных.

person JoelFan    schedule 12.08.2010