Виявлення та запобігання використанню кількох вікон або закладок браузера в веб додатках,

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

У розробці веб додатків існує кілька різних проблем безпеки, про які потрібно пам'ятати. Одне з правил свідчить:

"Обмежуйте користувача одним вікном браузера"

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

Є дві проблеми в тому, яким чином заборонити використання декількох вікон або вкладок:

    1. Яким чином ми можемо відрізнити два запити (GET or POST) на стороні сервера і дізнатися чи прийшов запит з того ж самого вікна або з нової вкладки?

2. Як зупинити користувача, який намагається ініціювати новий запит (GET or POST) з двох різних вікон на стороні клієнта?

      Нижче наведено приклад дій, які користувач може виконати для створення нового вікна або вкладки браузера:

Зауваження: Всі вікна відкриті з використанням зазначених вище команд поділяють одну і ту ж сесію / cookies в процесі спілкування з сервером. При створенні нової вкладки / вікна браузер використовує точну копію вихідної сторінки.

Рішення для визначення і запобігання використанню декількох вкладок або вікон веб браузера при роботі з веб додатком

Є два підходи, які ми можемо використовувати в веб додатку:

Детально, крок за кроком:

Спочатку запитується сторінка Default.aspx.

За подією page load виконується наступний скрипт:

У Default.aspx.cs у нас є функіця GetWindowName () яка повертає унікальне ім'я.

Коли сторінка завантажується перший раз window.name у Default.aspx містить пусте значення ( ""). Скрипт встановлює window.name в значення "default" і відкриває себе в тому ж самому вікні заново. Тепер унікальне ім'я вікна встановлено (використовуючи функцію на стороні сервера GetWindowName ()) і відкрита сторінка Home.aspx.

Скрипт закриває Default.aspx відразу після відкриття Home.aspx.

Зауваження: Кожен раз при відкритті нового вікна значення window.name порожнє ( ""). Якщо window.name пусте то Internet Explorer 7 і вище видає повідомлення про підтвердження:

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

На всіх інших сторінках (за винятком Default.aspx), ми використовуємо такий скрипт в секції Head.

І такий код у відповідному code-behind (Home.aspx.cs):

2. Перевірка HTTP Referer header на стороні сервера

Це дозволяє серверу створити список зворотних посилань для різних його потреб. І дозволяє відстежити «погані» посилання.

Якщо запит не має поля referer це означає що клопотання не був згенерований використовуючи такі опції:

(Зауваження: При відкритті сторінки в новому вікні або вкладці використовуючи центральну або праву кнопки миші значення UrlReferer буде доступно. Але window.name буде порожнім. І ця умова буде оброблено на стороні клієнта в скрипті, який ми вже додали на сторінку)

висновок

Описане рішення дозволяє заблокувати надходження використання більш ніж одного вікна браузера. Однак варто зауважити що все таки не існує 100% надійно працює способу виявити і запобігти використання декількох вікон браузера в рамках однієї програми.

Навігація по публікаціям

Поясніть мені, навіщо так робити?

Ми у себе в проекті навпаки, боремося щоб дозволити користувачеві відкривати сторінки в декількох вкладках.

Мене, особисто, завжди дратують такі обмеження.

Основна проблема в тому, що натискаю Ctrl-N користувач отримує точну копію відкритого в даний момент вікна.
А в цьому вікні користувач виявився на якомусь етапі процесу. Скажімо, потрібно послідовно провести користувача через 3 кроки.
Причому він вже знаходиться на другому кроці. Натиснув користувач CTRL-N і ми отримали ще одну копію вікна на другому кроці і ніяк не можемо відрізнити ці два вікна один від одного і вся послідовність кроків руйнується якщо користувач починає перемикатися між вікнами і натискати «продовжити»

Жесть какая ... Народ Не робіть ніколи так. Що WindowName, що UrlRererrer можна так само з легкістю підмінити, якщо кому-небудь потрібно буде.

Дивіться краще на архітектуру свого застосування, на правильність вибору платформи і т.п.

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

Підміна сесії - ви анітрохи не вирішуєте цю проблему
множинні запити-відповіді - теж саме, як ви вирішили цю проблему? (Якщо я розумію це DDoS атакою)
конкуренція даних - швидше за все хрінова архітектура додатки

Якщо дуже хочеться обмежити - ось готове рішення 🙂
А взагалі, якщо користувач відкрив форму редагування будь-якого об'єкта і «склоніровал» сторінку в іншу вкладку браузера. Натиснув на першій вкладці «зберегти», потім на другий теж. Яка вкладка повинна «перемогти» при збереженні даних? Що стане з якими то «проміжними» даними, які потрібні при збереженні сторінки і вже помінялися раніше при натисканні «зберегти» на першій сторінці?
Це ж не просто конкурентну збереження даних, у користувача точна копія першої сторінки та ми не можемо відрізнити її від оригіналу.
У HTML 5 для вирішення цієї проблеми можна використовувати сховище на стороні клієнта, яке НЕ клонують при створенні копії сторінки.
І це зроблено саме для вирішення описаної проблеми. Підкажіть, будь архітектурне рішення дозволить мені склоніровать сторінку і продовжити роботу вже на двох абсолютно ідентичних сторінках без руйнування контексту і я буду вам дуже вдячний!

Ну давайте розглянемо приклади.

1. Про редагування форми:
Припустимо ми на формі редагування користувача, заповнюємо поля.
Клонуємо сторінку.
На першій сторінці тиснемо Submit.
Якщо все добре у нас відбувається Update в базі даних.
На другій сторінці тиснемо Submit.
Якщо все добре у нас відбувається ще один Update в базі даних.
Разом: Вставлені два користувача.
Я щитаю така поведінка абсолютно логічним.

І це вже проблеми користувача, що він один і той же об'єкт двічі оновив.

Добре, якщо завдання таке, як ви описали то звичайно проблем немає взагалі і система може вести себе точно так як ви описали.
А тепер припустимо що у нас ситуація трохи інша (приведу спочатку приклад, який представили розробники стандарту HTML5):
Користувач знаходиться на сайті продажу квитків на літак і відкрив дві вкладки (склоніровал одну зі сторінок в іншу вкладку). Так як користувач може перемикатися з однієї вкладки в іншу і переходити з однієї вкладки на іншу може виникнути ситуація, коли він випадково або навмисно закриє одну з вкладок і бачачи другу буде повністю впевнений що квиток він не купив. Потенційно це може привести до того, що користувач купить два квитки замість одного ... Якби у нас був механізм який дозволить визначати що вкладка була «склонірована» ми могли б видати користувачеві попередження або виконати якісь інші дії, які допоможуть запобігти можливу помилку в діях користувача .
Або інший приклад: у нас є багатокроковий візард, стан об'єкта, який обробляє цей візард зберігається десь в сесії з прив'язкою до конкретної сторінці. Якщо користувач склоніровал сторінку ми отримаємо дві відрізняються один від одного сторінки, які працюють з одним і тим же об'єктом одночасно і одночасно змінюють його частини. При збереженні даних з однієї зі сторінок ми можемо потенційно отримати некоректний об'єкт в пам'яті. Щоб не допустити цього можна перевіряти реальний стан об'єкта і те, яке очікувалося при відправці запиту зі сторінки. Але знову таки якщо об'єкт дуже складний і в ньому дуже багато полів така перевірка може виявитися непростим завданням. В цьому випадку може мати сенс заборонити відкриття декількох вкладок для таких критичних сторінок.

Ну ось знову ви навели приклади які як раз можна реалізувати для клонованих вкладках без проблем.

Про замовлення квитків: ви пишете «коли він випадково або навмисно закриє одну з вкладок і бачачи другу буде повністю впевнений що квиток він не купив».
У нас наприклад оплата проходить через «кошик» куди користувач додає різні Items. І коли він на одній з вкладок проводить оплату, корзина очищається і дані про покупку копіюються вже куди потрібно. Так ось, якщо він на іншій вкладці знову натисне оплатити, то йому повернеться помилка, що його кошик порожній.

Про багатокроковий візард: сдесь так, з Візард все не просто. Доводиться розглядати купу умов і різних ситуацій. Тут і вперед / назад користувач в браузері може натиснути і вкладку нову відкрити, це його бажання.
Але ніхто і не говорив, що програмувати легко. Я вважаю так, якщо ви робите складний візард, вже постарайтеся його і реалізувати відповідним чином, а не обмежувати користувача.
У мене в проекті є складний візард, а й коду логіки там не просто багато, а дуже багато.

Загалом мені всетаки здається, що не варто користувачів обмежувати.

Ну, я от не розумію, Вадим, якщо Ви вирішили проблему декількох вкладок шляхом «нелегкого» програмування, то честь Вам і хвала! Тільки ось є купа прикладів ситуацій, в яких просто НЕ ТРЕБА користувачеві дублювати вкладки, то до чого тут обмеження, що накладаються на користувача? Навіщо його морочити кошиками якимись, себе морочити алгоритмами? Раз вже пишете, що програмування - нелегкий процес, так ускладнювати його немає сенсу (ИМХО) :).

Щось я не зрозумів цей код. Ви впевнені, що цей код робочий? І взагалі, що якщо користувач відкриє одну вкладку. Вкладка отримає свій ID, він буде записаний в сесію на стороні сервера. Потім користувач закриє цю вкладку і відкриє сторінку заново. Оскільки код сесії вже зберігається на сервері, то ми отримуємо InvalidAccess, тобто доступ до сайту взагалі блокується. Я вирішив задачу через localStorage (через jStorage.js для підтримки старих браузерів). При відкритті будь-якої сторінки завжди встановлюється прослушка змін глобальної змінної і після цього відправляється луна-запит (у вигляді установки змінної виду echo = random ()). Якщо приходить луна-відповідь (з іншої вкладки з такою ж функції прослушки у вигляді установки змінної block = random ()), то значить вже відкрита інша вкладка і ми блокуємо доступ до сайту. Тільки є нюанс, що деякі браузери типу IE викликають функцію прослушки навіть для змінної встановлених в цій же самій вкладці, а інші браузери немає, тому потрібно порівнювати random () відправляється і значення одержуваного, якщо вони збіглися, то це запит з цієї ж самої вкладки , якщо не співпали, значить відповідь прийшла з іншої вкладки. Працює код нормально і під будь-які живі браузери. Мінус в тому, що це не синхронний обмін запит-відповідями, а відповідь може прийти через деякий час або не прийти взагалі (якщо вкладка єдина), тому сторінка промальовується, скрипти починають працювати і потім вже (наприклад, через 1 секунду) отримуємо луна відповідь і блокуємо сторінку. Виходить мерехтіння. У нових браузерах всюди крім Firefox є затримка, в Firefox і в нових і в старих версіях обробка відбувається миттєво. В цілому реалізація робоча, але не ідеальна. Ідеального рішення схоже тут немає.