Sesje i bezpieczeństwo

Sesje dokumentów i kontrola dostępu

Token Doconut identyfikuje otwarty dokument. Jeśli jakiekolwiek żądanie niosące go zostanie obsłużone, osoba, która go uzyskała, mogłaby odczytać każdą stronę. Ta strona wyjaśnia, co zawiera sesja dokumentu, jak długo trwa oraz jak DocImage.axd decyduje, kto może zobaczyć jego strony — na jednym serwerze i w farmie sieciowej.

Co zawiera sesja dokumentu

Każde udane OpenDocumentAsync tworzy jedną sesję, przechowywaną w pamięci:

  • loaded format viewer — instancja silnika przechowująca przetworzony dokument,
  • per-page state — rotacja, odbicia i dane adnotacji, które użytkownik stosuje w widżecie,
  • opcjonalny search index, tworzony przy pierwszym wyszukiwaniu (lub wczytywany z wstępnie zbudowanego pliku .srh),
  • watermark sesji pochodzący z DocOptions.Watermark.

Okres życia

Sesje wygasają po przesuwanym oknie: DocOptions.TimeOut minut (domyślnie 60), resetowanym przy każdym żądaniu, które prezentuje token. Gdy sesja kończy się — z powodu wygaśnięcia lub CloseDocument(token) — jej silnik dokumentu jest zwalniany, a pamięć zwalniana natychmiast.

csharp
// Krótkotrwała sesja dla jednorazowego podglądu
var token = await viewer.OpenDocumentAsync(path, new DocOptions { TimeOut = 10 });

Żądanie z wygasłym tokenem otrzymuje obraz błędu z napisem Document session not found. Please re-open document.. Strona musi ponownie otworzyć dokument, aby uzyskać nowy token. Na pojedynczym serwerze, ponowne uruchomienie puli aplikacji kończy także wszystkie sesje.

Strony są serwowane wyłącznie sesji, która je otworzyła

Przy użyciu UnsafeMode = false — domyślnego — otwarcie dokumentu przyznaje dostęp do sesji ASP.NET żądania, które go otworzyło, poprzez zapisanie wpisu secure-{token} w tej sesji. DocImage.axd wymaga samego stanu sesji i serwuje stronę tylko wtedy, gdy sesja żądająca posiada ten wpis. Każda inna przeglądarka prezentująca ten sam token otrzymuje obraz błędu z napisem You Are Not Authorized To View This Page.

csharp
Doconut.DoconutHost.Initialize(options =>
{
    // false (domyślnie): DocImage.axd serwuje stronę tylko do sesji ASP.NET, która ją otworzyła
    // dokument. Obsługa żąda stanu sesji samodzielnie; potrzebuje tylko, aby strona nie
    // wyłączała stan sesji w Web.config.
    options.UnsafeMode = false;
});

Trzy praktyczne konsekwencje:

  • Punkt końcowy otwierania wymaga stanu sesji. Kontrolery MVC i strony Web Forms mają go domyślnie. Niestandardowy IHttpHandler nie posiada go, chyba że implementuje System.Web.SessionState.IRequiresSessionState. Otwarcie działające bez sesji nie ma czego przyznać, więc przeglądarka wyświetla „You Are Not Authorized To View This Page.” na każdej stronie.
  • Stan sesji musi pozostać włączony. <sessionState mode="Off" /> w Web.config wyłącza zarówno przyznanie, jak i sprawdzenie, a każda strona jest odrzucana.
  • Przeglądarka musi wysyłać ciasteczko sesji. Żądania widżetu są tego samego pochodzenia, więc automatycznie niosą ASP.NET_SessionId. Klient bez pojemnika na ciasteczka — skrypt, wywołanie serwer‑do‑serwera, konfiguracja cross‑origin usuwająca ciasteczka — jest odrzucany. To jest działająca funkcja.

UnsafeMode = true wyłącza sprawdzanie: każde żądanie niosące token jest obsługiwane. To jeden globalny przełącznik dla każdego dokumentu, bez możliwości nadpisania per dokument. Pozostaw go false.

Unieważnianie

CloseDocument(token) robi więcej niż zwalnia pamięć: usuwa wpis secure-{token} z sesji bieżącego żądania i unieważnia token, więc zamknięty token jest od razu odrzucany.

Farmy sieciowe: podpisane bilety

W farmie kolejny request może trafić na serwer, który nigdy nie widział dokumentu, w innym procesie roboczym, z własną sesją ASP.NET. Przyznanie sesji nie może go podążać. Tryb rozproszony zastępuje to podpisanym biletem powiązanym z przeglądarką: każdy węzeł współdzieli jedno miejsce przechowywania sesji dokumentów i jeden klucz podpisujący, a DocImage.axd akceptuje żądanie, gdy jego bilet jest ważny dla tego tokenu, tej przeglądarki i — gdy użytkownik jest zalogowany — tego użytkownika.

Konfiguracja, identyczna na każdym węźle:

csharp
// Global.asax.cs - Application_Start
Doconut.DoconutHost.Initialize(
    options => options.UnsafeMode = false,     // sprawdzanie biletu uruchamia się tylko, gdy jest to false
    services => services.AddDoconutDistributed(d =>
    {
        d.Store             = CloudLocation.FileShare;
        d.FileShareRootPath = @"\\fileserver\doconut-shared";  // folder, do którego może dotrzeć każdy węzeł
        d.SigningKey        = farmKey;                          // co najmniej 32 bajty, TAKIE SAME na każdym węźle
    }));

Punkt końcowy otwierania następnie wiąże bilet z przeglądarką przed otwarciem i zwraca bilet wraz z tokenem:

  • ustaw DocOptions.BrowserId = DoconutFarmBinding.EnsureBrowserId(System.Web.HttpContext.Current, securityOptions) — wydaje ciasteczko doconut-client, do którego bilet jest powiązany — oraz DocOptions.UserId = DoconutFarmBinding.ResolveUserId(System.Web.HttpContext.Current);
  • po OpenDocumentAsync, zwróć viewer.AccessToken razem z tokenem;
  • na stronie przekaż oba do widżetu: objViewer.View(token, access).

Doconut.MvcSample.WebFarm w pakiecie przykładów jest kompletnym, uruchamialnym węzłem farmy, a towarzyszący mu przewodnik migracji wyjaśnia każdy krok.

Trzymaj klucz podpisujący poza kontrolą wersji: służy zarówno do podpisywania biletów, jak i szyfrowania dokumentów w wspólnym magazynie. Węzeł z innym kluczem odrzuca każdy bilet wydany przez inne węzły.

Przetestuj farmę w prawdziwej przeglądarce lub skryptem, który zachowuje ciasteczka. Klient bez ciasteczka doconut-client nie otrzymuje odpowiedzi 401: otrzymuje obraz błędu „not authorized” ze statusem 200, około 2 KB, podczas gdy prawdziwa strona ma dziesiątki do setek kilobajtów.

Lista kontrolna produkcji

  • Zachowaj UnsafeMode = false (domyślne).
  • Otwieraj dokumenty z żądań, które mają stan sesji, i nigdy nie wyłączaj stanu sesji.
  • Upewnij się, że ciasteczko sesji dociera do DocImage.axd (to samo pochodzenie, HTTPS, polityka SameSite, która to umożliwia).
  • W farmie używaj trybu rozproszonego z jednym kluczem podpisującym współdzielonym przez wszystkie węzły.
  • Wywołaj CloseDocument, gdy użytkownik opuszcza dokument — korzyść zarówno dla pamięci, jak i bezpieczeństwa.
  • Nigdy nie loguj ani nie udostępniaj tokenów; traktuj je jako krótkotrwałe poświadczenia.

Czy ta strona była pomocna?