Сессии и безопасность

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

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

Что хранит сеанс документа

Каждый успешный вызов OpenDocumentAsync создаёт один сеанс, хранящийся в памяти:

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

Время жизни

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

csharp
// A short-lived session for a one-shot preview
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 (the default): DocImage.axd serves a page only to the ASP.NET session that opened
    // the document. The handler asks for session state itself; it only needs the site not to
    // turn session state off in Web.config.
    options.UnsafeMode = false;
});

Три практических следствия:

  • Точка входа открытия требует состояния сеанса. MVC‑контроллеры и страницы Web Forms имеют его по умолчанию. Пользовательский IHttpHandler не имеет его, если не реализует System.Web.SessionState.IRequiresSessionState. Открытие без сеанса ничего не может предоставить, поэтому просмотрщик показывает «Вы не авторизованы для просмотра этой страницы» на каждой странице.
  • Состояние сеанса должно оставаться включённым. <sessionState mode="Off" /> в Web.config отключает как предоставление, так и проверку, и каждая страница отклоняется.
  • Браузер должен отправлять cookie сеанса. Запросы виджета являются запросами того же происхождения, поэтому они автоматически несут ASP.NET_SessionId. Клиент без «cookie‑банки» — скрипт, сервер‑к‑серверу вызов, кросс‑доменные настройки, которые отбрасывают cookies — отклоняется. Это и есть работающая функция.

UnsafeMode = true отключает проверку: любой запрос с токеном обслуживается. Это глобальный переключатель для всех документов, без возможности переопределения на уровне отдельного документа. Оставляйте его false.

Отзыв токена

CloseDocument(token) делает больше, чем освобождает память: он удаляет запись secure-{token} из текущей сессии запроса и аннулирует токен, поэтому закрытый токен сразу отклоняется.

Веб‑фермы: подписанные билеты

В ферме следующий запрос может попасть на сервер, который никогда не видел документ, в другом рабочем процессе, со своей собственной ASP.NET‑сессией. Предоставление сеанса не может его последовать. Распределённый режим заменяет его подписанным билетом, привязанным к браузеру: каждый узел использует общий хранилище сеансов документов и общий ключ подписи, а DocImage.axd принимает запрос, когда его билет действителен для данного токена, данного браузера и — если пользователь вошёл — для данного пользователя.

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

csharp
// Global.asax.cs - Application_Start
Doconut.DoconutHost.Initialize(
    options => options.UnsafeMode = false,     // the ticket check only runs when this is false
    services => services.AddDoconutDistributed(d =>
    {
        d.Store             = CloudLocation.FileShare;
        d.FileShareRootPath = @"\\fileserver\doconut-shared";  // a folder every node can reach
        d.SigningKey        = farmKey;                          // at least 32 bytes, the SAME on every node
    }));

Точка входа открытия затем привязывает билет к браузеру перед открытием и возвращает билет вместе с токеном:

  • установить 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 в пакете примеров — полноценный, готовый к запуску узел фермы, а сопровождающий его гид по миграции объясняет каждый шаг.

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

Протестируйте ферму реальным браузером или скриптом, сохраняющим cookies. Клиент без cookie doconut-client не получает ответ 401: он получает изображение‑ошибку «не авторизован» со статусом 200, размером около 2 KB, тогда как реальная страница весит от десятков до сотен килобайт.

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

  • Оставляйте UnsafeMode = false (значение по умолчанию).
  • Открывайте документы из запросов, имеющих состояние сеанса, и никогда не отключайте состояние сеанса.
  • Убедитесь, что cookie сеанса достигает DocImage.axd (тот же источник, HTTPS, политика SameSite, позволяющая это).
  • В ферме используйте распределённый режим с одним общим ключом подписи для всех узлов.
  • Вызывайте CloseDocument, когда пользователь покидает документ — это улучшает как память, так и безопасность.
  • Никогда не записывайте и не делитесь токенами; рассматривайте их как краткоживущие учётные данные.

Была ли эта страница полезной?