Сесії та безпека

Сесії документів та контроль доступу

Токен Doconut ідентифікує відкритий документ. Якщо будь‑який запит, що містить його, буде обслуговуватись, той, хто його отримав, зможе прочитати кожну сторінку. На цій сторінці пояснюється, що містить сесія документа, як довго вона живе і як DocImage.axd вирішує, хто може бачити його сторінки — на одному сервері та у веб‑фермі.

Що містить сесія документа

Кожен успішний виклик OpenDocumentAsync створює одну сесію, що зберігається в пам’яті:

  • завантажений переглядач формату — екземпляр рушія, що тримає розпарсений документ,
  • стан кожної сторінки — обертання, відзеркалення та дані анотацій, які користувач додає у віджеті,
  • опційний індекс пошуку, створений під час першого пошуку (або завантажений із попередньо створеного файлу .srh),
  • водяний знак сесії з DocOptions.Watermark.

Тривалість життя

Сесії завершуються за ковзним вікном: DocOptions.TimeOut хвилин (за замовчуванням 60), що скидається кожним запитом, який подає токен. Коли сесія закінчується — через прострочення або через CloseDocument(token) — її рушій документа звільняється, а пам’ять звільняється негайно.

csharp
// Короткоживуча сесія для одноразового попереднього перегляду
var token = await viewer.OpenDocumentAsync(path, new DocOptions { TimeOut = 10 });

Запит із простроченим токеном отримує зображення‑помилку з текстом Document session not found. Please re-open document. Сторінка повинна знову відкрити документ, щоб отримати новий токен. На одному сервері перезапуск пулу додатків також завершує всі сесії.

Сторінки подаються лише тій сесії, яка їх відкрила

При UnsafeMode = false — значення за замовчуванням — відкриття документа надає доступ ASP.NET‑сесії запиту, який його відкрив, шляхом запису елементу secure-{token} у цю сесію. DocImage.axd потребує стан сесії і подає сторінку лише тоді, коли запитуюча сесія містить цей елемент. Будь‑який інший браузер, що подає той самий токен, отримує зображення‑помилку з текстом You Are Not Authorized To View This Page.

csharp
Doconut.DoconutHost.Initialize(options =>
{
    // false (за замовчуванням): DocImage.axd подає сторінку лише тій ASP.NET‑сесії,
    // яка відкрила документ. Обробник сам запитує стан сесії; важливо, щоб у Web.config
    // не вимикали стан сесії.
    options.UnsafeMode = false;
});

Три практичні наслідки:

  • Точка входу відкриття потребує стану сесії. MVC‑контролери та сторінки Web Forms мають його за замовчуванням. Користувацький IHttpHandler не має, якщо не реалізує System.Web.SessionState.IRequiresSessionState. Відкриття без сесії нічого не може надати, тому переглядач показує «You Are Not Authorized To View This Page.» на кожній сторінці.
  • Стан сесії має залишатися ввімкненим. <sessionState mode="Off" /> у Web.config вимикає і надання, і перевірку, і кожна сторінка відхиляється.
  • Браузер має надсилати cookie сесії. Запити віджету є same‑origin, тому вони автоматично несуть ASP.NET_SessionId. Клієнт без «cookie‑банки» — скрипт, сервер‑до‑сервера виклик, крос‑оригінальна конфігурація, що відкидає cookie — відхиляється. Це і є робоча функція.

UnsafeMode = true вимикає перевірку: будь‑який запит з токеном обслуговується. Це глобальне налаштування для всіх документів, без можливості переоприділення на рівні окремого документа. Залишайте його false.

Відкликання

CloseDocument(token) робить більше, ніж звільняє пам’ять: воно видаляє елемент secure-{token} із сесії поточного запиту та відкликає токен, тому закритий токен одразу відхиляється.

Веб‑ферми: підписані квитки

У фермі наступний запит може потрапити на сервер, який ніколи не бачив документ, у іншому процесі‑робітнику зі своєю ASP.NET‑сесією. Надання сесії не може його переслідувати. Режим розподілу замінює його підписаним квитком, прив’язаним до браузера: кожен вузол спільно використовує один сховище сесій документів і один ключ підпису, а DocImage.axd приймає запит, коли його квиток дійсний для цього токену, цього браузера і — коли користувач увійшов — для цього користувача.

Налаштування, ідентичне на кожному вузлі:

csharp
// Global.asax.cs - Application_Start
Doconut.DoconutHost.Initialize(
    options => options.UnsafeMode = false,     // перевірка квитка виконується лише коли це false
    services => services.AddDoconutDistributed(d =>
    {
        d.Store             = CloudLocation.FileShare;
        d.FileShareRootPath = @"\\fileserver\doconut-shared";  // папка, доступна всім вузлам
        d.SigningKey        = farmKey;                          // щонайменше 32 байти, ОДИНАКОВИЙ на всіх вузлах
    }));

Точка входу відкриття тоді прив’язує квиток до браузера перед відкриттям і повертає квиток разом з токеном:

  • встановіть DocOptions.BrowserId = DoconutFarmBinding.EnsureBrowserId(System.Web.HttpContext.Current, securityOptions) — це створює cookie doconut-client, до якого прив’язаний квиток — і DocOptions.UserId = DoconutFarmBinding.ResolveUserId(System.Web.HttpContext.Current);
  • після OpenDocumentAsync поверніть viewer.AccessToken разом з токеном;
  • у сторінці передайте обидва у віджет: objViewer.View(token, access).

Doconut.MvcSample.WebFarm у пакеті прикладів — це повний, готовий до запуску вузол ферми, а посібник з міграції поруч пояснює кожен крок.

Тримайте ключ підпису поза системою контролю версій: він підписує квитки і шифрує документи у спільному сховищі. Вузол з іншим ключем відхиляє всі квитки, видані іншими.

Тестуйте ферму реальним браузером або скриптом, що зберігає cookie. Клієнт без cookie doconut-client не отримує відповіді 401: він отримує зображення‑помилку «not authorized» зі статусом 200, розміром близько 2 KB, тоді як реальна сторінка важить десятки‑сотні кілобайт.

Чек‑лист для продакшну

  • Тримайте UnsafeMode = false (за замовчуванням).
  • Відкривайте документи лише з запитів, які мають стан сесії, і ніколи не вимикайте стан сесії.
  • Переконайтеся, що cookie сесії доходить до DocImage.axd (same origin, HTTPS, політика SameSite, що дозволяє це).
  • У фермі використовуйте розподілений режим з одним спільним ключем підпису для всіх вузлів.
  • Викликайте CloseDocument, коли користувач залишає документ — це корисно і для пам’яті, і для безпеки.
  • Ніколи не журналюйте і не поширюйте токени; розглядайте їх як короткоживучі облікові дані.

Чи була ця сторінка корисною?