Czym są cookies oraz jak działają sesje użytkownika
Ta lekcja będzie o tyle nietypowa, że nie będziemy chwilowo dalej rozwijać naszej aplikacji. Może polecę parę ćwiczeń dla chętnych, ale co do zasady skupimy się na czymś innym. Po pierwsze, spróbujemy wspólnie zrozumieć co to są i jak działają cookies. Po drugie, poznamy dokładniej narzędzia, które pozwolą nam zobaczyć, jak działa nasza strona pod maską przeglądarki. Na koniec rozważymy pozyskane informacje w kontekście przechowywania informacji na temat użytkownika naszej aplikacji.
Przechowywanie “stanu” w aplikacjach internetowych
Póki co uczyliśmy się o protokole HTTP jako sekwencjach wysyłania żądań do serwera o daną ścieżkę i otrzymywaniu odpowiedzi na podstawie tylko tej informacji. W takim modelu mentalnym, aplikacja webowa działa tak, że jeżeli skopiuję link z okna przeglądarki, wyślę go do znajomego i on otworzy go w swojej przeglądarce, powinniśmy zobaczyć tę samą stronę, o bardzo zbliżonej treści. To wygodny model, ale kompletnie niezgodny z naszym codziennym doświadczeniem korzystania z internetu.
Stan (ang. state) w programowaniu oznacza informacje przechowywane w systemie. Mówimy o systemie, że jest bezstanowy (stateless) jeżeli korzystanie z jego różnych aspektów jest niezależne od poprzednich działań. Wywołanie jakiegoś API zawsze zwróci ten sam wynik, niezależnie od poprzednich operacji. W przypadku systemów przechowujących stan (stateful), wyniki działań kolejnych operacji będą zależeć od poprzednich działań. W przypadku sklepu internetowego przykładami funkcjonalności zależnych od stanu może to być wypełniający się koszyk, historia przeglądanych produktów albo system rekomendacji oparty na poprzednich zamówieniach.
Jeżeli ja wpiszę adres youtube.com w swojej przeglądarce, to dostanę zupełnie inną stronę niż kolega zalogowany na swoje konto, albo ktoś, kto nie ma konta w ogóle. Aplikacje internetowe ewidentnie gdzieś przechowują informacje o tym, kto z nich korzystał ostatnio w danej przeglądarce i na podstawie tego ułatwiają nam korzystanie ze swoich usług. Ale jak dokładnie to działa?
“Pliki” cookies
Nie wiem, kto pierwszy użył określenia “pliki cookies”, ale w najlepszym wypadku udało mu się wprowadzić zamieszanie w dziedzinie technologii, która już na start nie jest zbyt jasna.
Cookies, w telegraficznym skrócie, to informacje, które serwer wysyła do naszej przeglądarki, a przeglądarka zobowiązuje się odesłać te same informacje następnym razem, kiedy będzie wysyłać żądania do tego samego serwera.
Wchodząc nieco głębiej, zacznijmy od informacji, że cookies jako mechanizm przechowywania stanu w protokole HTTP są opisane w dokumencie RFC 6265. Polecam jak zwykle lekturę, ale ostrzegam, że większość dokumentu to rozważania na temat tego, jakie znaki są dozwolone w Cookies i dookreślaniu innych drobnych szczegółów.
Wracając do protokołu HTTP, czyli tego jak nasze przeglądarki rozmawiają z serwerami internetowymi aby dostarczyć nam codziennie nowe porcje memów i filmików z kotami… Kiedy wpisuję w przeglądarkę adres strony, którą chcę odwiedzić, czyli URI, moja przeglądarka konstruuje na tej podstawie wiadomość, którą następnie wysyła do serwera gdzieś po drugiej stronie internetu.
Co się składa na tę wiadomość? Jedna linijka żądania HTTP (request line), a następnie szereg nagłówków HTTP (HTTP headers).
Nagłówek HTTP składa się z dwóch części, nazwy i zawartości, oddzielonych dwukropkiem i spacją:
Nazwa-Naglowka: zawartosc
Przykładowa wymiana Cookies
Gdybym chciał odwiedzić stronę https://www.bookfinder.com/
, żądanie HTTP wysłane przez moją przeglądarkę może wyglądać w ten sposób:
1GET / HTTP/2
2Host: www.bookfinder.com
3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0
4Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
5Accept-Language: pl,en-US;q=0.7,en;q=0.3
6Accept-Encoding: gzip, deflate, br, zstd
7Connection: keep-alive
8Cookie: search_prefs=pr_currency&PLN&pr_destination&pl&pr_mode&basic&pr_il&en&pr_lang&en&pr_search_ebooks&1; test-default=b; test-el=a; test-pf=a; recent_en=d&eJzT1dVVUFQsSC3K0c9ILM6winT09VGoruXS1dXl0lXILE7Ks0KRd8rPz3bLzEtJLbKyckksSbSy8gx28uNSAAIQw0pB3dDS2MLE3NzA3FgdLmxogEvC2ErB0twCLmUElYkPc/TxdLFSMATz/YM83T39HH0wDMlJzEu3UkjNAzLz8uMhrjWAcAryU0BssvwAdIeRiYGhkaUlig%2BwCUPcD5cxJuB%2BFCMIux4Axx1lYw%3D%3D; rand_seed=88703489892476872991
9Upgrade-Insecure-Requests: 1
10Sec-Fetch-Dest: document
11Sec-Fetch-Mode: navigate
12Sec-Fetch-Site: none
13Sec-Fetch-User: ?1
Jak widać jest tu dość dużo informacji. Od razu mogą nam się rzucić w oczy następujące rzeczy:
- w linijce 1 widzimy, że przeglądarka wysłała żądanie wykorzystując protokół HTTP 2.0, prosząc o ścieżkę
'/'
metodą HTTP GET - w linijce 2 wyszczególniona jest nazwa serwera, do którego kierujemy żądanie o stronę.
- w linijce 8 widzimy nagłówek HTTP o nazwie Cookie o długiej zawartości.
- reszta nagłówków nas w tym momencie nie interesuje.
- może nie jest to jasno widoczne, ale przeglądarka kończy żądanie jedną pustą linią żeby podkreślić, że nie ma więcej nagłówków do przesłania.
Po co nagłówek Host
mówiący o tym, do jakiego serwera kierujemy żądanie? Zanim nasza przeglądarka porozumie się z serwerem, nasz komputer używając DNS musi zlokalizować adres IP serwera i przekazać go przeglądarce. Ale skoro już jesteśmy w stanie zlokalizować serwer i wysłać do niego wiadomość, a serwer wie, jaką stronę serwuje, to czy nie jest to zbędna informacja? Na pierwszy rzut oka tak, ale fizycznie jeden serwer może obsługiwać wiele domen, dlatego może potrzebować tej informacji, żeby zwrócić nam tę zawartość, której szukamy.
Jeżeli się przyjrzeć, zawartość nagłówka Cookie składa się z ciągu wyrażeń w postaci nazwa1=wartosc1
oddzielonych znakami ;<spacja>
. Znajomość języka angielskiego pozwala nam wydedukować nieco informacji na temat przeznaczenia danych, ale na tym też nie będziemy się teraz skupiać.
Właściwe pytanie brzmi, skąd moja przeglądarka wzięła te informacje? Wiemy już, że otrzymała je od serwera strony bookfinder, ale kiedy i jak dokładnie?
Uprośćmy nieco sytuację i użyjmy trochę prostszego narzędzia do komunikacji z serwerami HTTP, mianowicie Linuxowego narzędzia cURL.
Zacznijmy od wpisania w terminalu Linuxowym następującej komendy:
$ curl -s --http1.1 https://www.bookfinder.com/ | head -10
W wyniku wykonania komendy powinniśmy zobaczyć pierwsze 10 wierszy odpowiedzi na żądanie HTTP wysłane do serwera strony bookfinder.com.
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js ie lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]> <html class="no-js ie lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]> <html class="no-js ie lt-ie9" lang="en"> <![endif]-->
<!--[if IE 9]> <html class="no-js ie9" lang="en"> <![endif]-->
<!--[if gt IE 9]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>BookFinder.com: New & Used Books, Rare Books, Textbooks</title>
Ale to tylko dokument HTML, jeżeli przeczytaliśmy cokolwiek na temat protokołu HTTP, wiemy że powinniśmy zobaczyć coś poza tym!
Aby zobaczyć dodatkowo nagłówki HTTP odesłane nam przez serwer, dodajmy do komendy flagę -i
$ curl -is --http1.1 https://www.bookfinder.com/ | head -10
Szybko się okazuje, że 10 wierszy to za mało, żeby odczytać wszystkie nagłówki, zmodyfikujmy drugą część komendy i ograniczmy wyjście do 25 wierszy.
$ curl -is --http1.1 https://www.bookfinder.com/ | head -25
W wyniku wykonania tej komendy na ekranie możemy zobaczyć następującą odpowiedź:
1HTTP/1.1 200 OK
2Content-Type: text/html; charset=utf-8
3Content-Length: 50228
4Connection: keep-alive
5Date: Tue, 03 Dec 2024 23:43:56 GMT
6X-Frame-Options: sameorigin
7Server: nginx/1.22.1
8Vary: Accept-Encoding
9Vary: User-Agent
10Set-Cookie: search_prefs=pr_currency&USD&pr_destination&us&pr_il&en&pr_lang&en&pr_search_ebooks&1; Domain=.bookfinder.com; Expires=Wed, 03-Dec-2025 23:43:56 GMT; Path=/
11Set-Cookie: test-default=a; Domain=.bookfinder.com; Expires=Fri, 03-Jan-2025 09:43:56 GMT; Path=/
12Set-Cookie: test-el=a; Domain=.bookfinder.com; Expires=Fri, 03-Jan-2025 09:43:56 GMT; Path=/
13Set-Cookie: test-pf=a; Domain=.bookfinder.com; Expires=Fri, 03-Jan-2025 09:43:56 GMT; Path=/
14Strict-Transport-Security: max-age=15768000; includeSubDomains
15Content-Security-Policy: default-src 'self' *.bookfinder.com; img-src https://d3uahvj51kpljk.cloudfront.net www.googletagmanager.com *.abebooks.com; style-src 'self' https://d3uahvj51kpljk.cloudfront.net https://*.google-analytics.com https://*.googletagmanager.com 'unsafe-inline'; script-src 'self' https://*.googletagmanager.com 'unsafe-inline' 'unsafe-eval'; font-src https://d3uahvj51kpljk.cloudfront.net/; connect-src 'self' https://analytics.google.com https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://unagi.amazon.com/1/events/com.amazon.csm.csa.prod
16X-Cache: Miss from cloudfront
17Via: 1.1 b97fc91ec89e8dcf1c9e7c533bd354fa.cloudfront.net (CloudFront)
18X-Amz-Cf-Pop: WAW51-P3
19X-Amz-Cf-Id: Rf-m_LRTvufprwecG5glpn0T2QHiwbnBaOy9_-c9U8PV_Sf6Ppgn-Q==
20
21<!DOCTYPE html>
22<!--[if lt IE 7]> <html class="no-js ie lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
23<!--[if IE 7]> <html class="no-js ie lt-ie9 lt-ie8" lang="en"> <![endif]-->
24<!--[if IE 8]> <html class="no-js ie lt-ie9" lang="en"> <![endif]-->
25<!--[if IE 9]> <html class="no-js ie9" lang="en"> <![endif]-->
Widzimy w 1 linijce odpowiedź serwera wraz z kodem statusu 200, oznaczającym że żądanie zostało przetworzone pomyślnie. W linijce 10 widzimy zestaw nagłówków Set-Cookie, których wartości są zbieżne z tym, co widzieliśmy w naszym żądaniu.
A więc stąd przeglądarka, której używałem aby odwiedzić stronę bookfinder.com wzięła te informacje. W odpowiedzi na żądanie HTTP serwer może odesłać przeglądarce informację, aby ta zapamiętała pewne informacje na przyszłość i przesłała je z powrotem do serwera przy następnych żądaniach.
W tym przypadku serwis pozwala użytkownikom zapamiętać swoje preferencje, tak długo jak korzystają ze strony z tej samej przeglądarki. Jest to wygodna opcja, jeżeli ktoś np. często zamawia produkty z danego serwisu, żeby nie musiał za każdym razem wprowadzać informacji, do jakiego kraju będzie trzeba wysłać zamawiane produkty.
Innymi dobrymi przykładami zastosowania cookies mogą być preferencje odnośnie schematu kolorystycznego strony albo domyślnego języka serwisu. A jakie są złe przykłady zastosowania cookies?
Jakich informacji nie trzymać w cookies?
Pierwszą rzeczą, o której musimy pamiętać myśląc o tym, co umieścimy w cookies jest fakt, że są to informacje przechowywane na danej przeglądarce użytkownika, więc weźmy pod uwagę, czego racjonalnie użytkownik może się spodziewać w interakcji z naszym serwisem na dwóch różnych urządzeniach. W przykładowym serwisie z filmami preferowany przez użytkownika domyślny rozmiar odtwarzacza wideo może się różnić w zależności od tego, czy korzysta właśnie z tabletu czy z komputera stacjonarnego. Ale już “ostatni oglądany film”? Jeżeli ktoś wychodzi z domu i chciałby kontynuować korzystanie z naszego serwisu na komórce, to może go zirytować, że komórka próbuje uruchamiać zupełnie inny film, niż oglądał przed chwilą.
Czas życia cookies
Oprócz nazwy i wartości Set-Cookie pozwala także na ustalenie, kiedy przeglądarka ma zapomnieć podane wartości. Serwer określa to podając po nagłówku Set-Cookie
atrybut Expires
i określając datę, kiedy dana wartość “wygaśnie”. W przypadku ustawień kolorystycznych czy innych preferencji może nie ma to większego znaczenia, ale domyślnie, jeżeli nie podana zostanie data wygaśnięcia wartości, ciasteczko powinno być zapomniane wraz z “końcem sesji User-Agent”, co dla większości przeglądarek oznacza zamknięcie zakładki ze stroną albo okna przeglądarki.
Oznacza to, że jeżeli chcemy aby wartość przetrwała zamknięcie przeglądarki czy reset komputera użytkownika, koniecznie musimy podać datę wygaśnięcia w “rozsądnej” odległości czasowej.
Alternatywnie możemy użyć atrybutu Max-Age, który określa po jakiej ilości sekund wartość wygaśnie.
Kasowanie cookies
Usuwanie cookies przez serwer jest mało intuicyjne, ponieważ aby usunąć takowe, serwer powinien zwrócić odpowiedź na żądanie z datą wygaśnięcia cookie w przeszłości. Z tego powodu ważne jest żeby przypilnować, że na naszym serwerze jest poprawnie ustawiona data i godzina oraz nasza aplikacja poprawnie korzysta z zegara systemowego.
Ograniczenie cookie do konkretnych zasobów
Serwer może zdefiniować, dla jakich domen obowiązuje dane cookie, oraz dla jakich ścieżek i “podścieżek” cookie powinno być wysyłane w żądaniach do serwera.
Atrybut Domain
pozwala ograniczyć cookies np. tylko do usług poczty w danej domenie: Domain=mail.domena.com
.
Atrybut Path
podobnie pozwala na ograniczenie do jakich ścieżek zostanie wysłana dana wartość cookie. Podając Path=/polls/
w naszej aplikacji oznaczałoby ustalenie, że dane cookie ma trafiać tylko do naszej aplikacji polls
i jej podścieżek, ale już nie do aplikacji admin
.
Atrybuty zabezpieczające cookies
Informacje zawarte w cookies możemy zabezpieczyć na dwa sposoby, dodając do pliku atrybuty HttpOnly
oraz Secure
.
Ustawiony atrybut HttpOnly
oznacza, że dane cookie nie będzie dostępne dla kodu JavaScript działającego na stronie internetowej.
Ustawiony atrybut Secure
oznacza, że cookie będzie przesyłane do użytkownika tylko bezpiecznymi kanałami, w dużym uproszczeniu tylko dla stron odwiedzianych przez HTTPS.
Jak dokładnie omowimy sobie jak działają te zabezpieczenia w rozdziale o bezpieczeństwie sesji użytkownika, kiedy omówimy scenariusze ataków na użytkowników naszego serwisu.
Wady cookies
Wiemy już, że przeglądarka internetowa jest zobowiązana odsyłać cookies do serwera z każdym żądaniem użytkownika. Jeżeli serwer chce utrzymać pliki cookies przy życiu np. przez miesiąc, będzie nam je odsyłał za każdym razem z przesuniętą datą wygaśnięcia o miesiąc od aktualnej daty i godziny. Oznacza to, że do każdego zapytania przeglądarki i do każdej odpowiedzi będziemy doklejać te same informacje. Jeżeli używamy plików cookies do przechowania preferencji użytkownika i liczba tych preferencji będzie duża, to komunikacja z naszym serwerem będzie wymagała dodatkowego pasma internetowego.
Kolejną kwestią jest potencjalna zawodność cookies do przechowywania informacji. Użytkownik przeglądarki może:
- zupełnie wyłączyć w swojej przeglądarce obsługę wszelkich cookies.
- korzystać z naszej aplikacji w trybie incognito/prywatnym co spowoduje usunięcie cookies niezależnie od ich atrybutów
- usunąć cookies dla naszej strony albo ręcznie, albo poprzez czyszczenie systemu operacyjnego
- ręcznie zmodyfikować wartość cookies w swojej przeglądarce
W związku z tym w cookies nie możemy przechowywać danych, których utrata byłaby poważnym problemem dla funkcjonowania aplikacji, ani danych, których użytkownik nie powinien modyfikować ręcznie
Sesje użytkownika
Informacje zawarte w cookies mają swoje zastosowanie, ale większość informacji na temat użytkownika będziemy chcieli przechować na serwerze. Jak w takim razie powiązać konkretnego użytkownika z kolejnymi zapytaniami do naszej aplikacji? Rozwiązaniem są tzw. sesje użytkownika.
Uwaga, nie mówimy tu o ustaleniu tożsamości użytkownika. Ten proces jest nazywany autentykacją i będziemy o nim mówić na następnej lekcji. Django jako framework wspiera anonimowe sesje użytkownika.
Tworzenie i utrzymywanie sesji użytkownika
W momencie wysłania pierwszego żądania HTTP do naszej aplikacji przeglądarka nie wyśle do naszego serwera żadnych informacji w cookies. Nasza aplikacja odczyta żądanie, nie znajdzie żadnych informacji w cookies, po czym do odpowiedzi dopisze cookie o przykładowej nazwie sessionId
o wartości ciągu losowych znaków. Przy następnych żądaniach przeglądarka odeśle nam ten sam ciąg znaków i na jego podstawie będziemy mogli powiązać to żądanie z poprzednimi działaniami tego użytkownika.
Przy kolejnych odpowiedziach na żądania będzie też możliwe przedłużenie sesji, o ile ma to sens w kontekście naszej aplikacji. Zaraz wrócimy do tego, co przemawia za, a co przeciw utrzymywaniu długowiecznych sesji użytkownika.
Dane powiązane z sesjami użytkownika
Informacje powiązane z sesją użytkownika domyślnie w frameworku Django trafiają do bazy danych, skąd wydobywane są za każdym razem, kiedy nasza aplikacja po nie sięga podczas przetwarzania kolejnych żądań. Istnieją inne metody przechowywania informacji takie jak cache typu key-value store, np. Redis, albo przechowywanie danych bezpośrednio w cookie zabezpieczonym przy pomocy podpisu kryptograficznego. Wszytkie te metody mają swoje wady, ale podjęcie poinformowanej decyzji na temat optymalnego rozwiązania dla swoich aplikacji będzie łatwiejsze wraz z doświadczeniem
W przypadku zapisu danych do bazy danych, problemem może być czas odczytu danych z dysku, oraz konieczność zapisywania ich każdym razem, kiedy użytkownik zmieni coś powiązanego z sesją użytkownika; Będzie to odczuwalnie spowalniało przetwarzanie żądań i może podrażać koszty funkcjonowania serwera. Dodatkowo przechowywanie dużej ilości danych dla każdego użytkownika może wystawić nas na atak typu Denail-of-Service polegający na tworzeniu dużej ilości sesji w celu zapełnienia przestrzeni dyskowej na naszym serwerze, albo przynajmniej obciążenie nas dodatkowymi kosztami za funkcjonowanie serwisu.
W przypadku cache’y key-value store informacje powiązane z sesją są przechowywane w pamięci RAM serwera, co oznacza że w przypadku nagłego restartu serwera wszystkie sesje zostaną utracone, co dla funkcjonowania aplikacji może być lekką niedogodnością, albo poważnym problemem doświadczenia użytkownika. Tu również istnieje ryzyko ataku nastawionego na zwiększenie zużycia zasobów naszej aplikacji, ale ponieważ dane nie trafiają na dysk, trudniejsze będzie wyłączenie aplikacji z użycia na dłuższy czas.
Jest możliwa jednocześnie opcja hybrydowa, gdzie dane trafiają i do cache, a następnie do bazy danych w celu zapewnienia z jednej strony szybszego zapisu/odczytu, a z drugiej trwałości danych. Ta opcja z kolei oznacza bardziej skomplikowany i droższy w utrzymaniu system.
W przypadku przechowywania danych w podpisanym kryptograficznie cookie oznacza to mniejsze zużycie dysku czy RAMu na serwerze, ale powoduje zwiększone zużycie pasma przesyłu danych, a także konieczność wykonywania potencjalnie kosztownych operacji kryptograficznych przy przetwarzaniu żądań HTTP. Ogranicza to też maksymalną ilość danych powiązanych z sesją, ponieważ przeglądarki ograniczaja wielkość pojedynczego cookie do kilku kilobajtów.
Jak widać, nie ma idealnego rozwiązania i wybranie tego najmniej złego będzie wymagało większego doświadczenia w tworzeniu aplikacji webowych.
Bezpieczeństwo sesji
Pierwszym i najważniejszym zagrożeniem w przypadku sesji użytkownika jest możliwość przejęcia cudzej sesji.
Jeżeli tworzymy sklep internetowy albo aplikację bankową, z oczywistych powodów nie chcemy, żeby ktoś mógł się podszyć za naszych użytkowników i narażać ich na straty finansowe. Wystarczy że nasza aplikacja pozwala na kontakt z innymi użytkownikami, a samo to może być wykorzystane jako potencjalny kanał oszustwa. Z tych powodów musimy zabezpieczyć naszych użytkowników przed takimi zagrożeniami.
W przypadku protokołu HTTP identyfikator sesji doklejony jako cookie trafia do naszego serwera jako jawny tekst. Gdyby ktoś korzystał z tej samej sieci Wi-Fi co użytkownik naszego portalu, mógłby podejrzeć jego żądanie HTTP, a następnie ustawić takie samo cookie w swojej przeglądarce. Na szczęście jest to problem łatwo rozwiązany poprzez stosowanie szyfrowanych połączeń TLS, co od lat jest tanie i stosunkowo proste do skonfigurowania.
Aby uniknąć możliwości zgadnięcia identyfikatora sesji innego użytkownika, należy wybierać długie i losowe identyfikatory, a także ograniczać czasy życia sesji do sensownych wartości. W przypadku serwisu rozrywkowego utrzymywanie długiej sesji może być dopuszczalne, ale sesja użytkownika klienta banku nie powinna być utrzymywana w nieskończoność.
Ostatnim poruszanym tu mechanizmem ataku będzie możliwość wydobycia cookie przez złośliwy kod JavaScript a następnie przesłania danych do serwera kontrolowanego przez osoby atakujące naszą aplikację. Jeżeli atakującym uda się w jakiś sposób uruchomić na naszej stronie swój kod JavaScript, wtedy poprzez API DOM mogą dostać się do wartości cookies i przesłać je dalej przy pomocy żądania AJAX. Możemy uniemożliwić taki dostęp dodając do naszego cookie atrybut HttpOnly
.
Dużo pełniejsze omówienie tematu bezpiecznego zarządzania sesjami użytkownika można znaleźć na stronie organizacji OWASP zajmującej się bezpieczeństwem aplikacji internetowych.
Zadania praktyczne
- Na poprzedniej lekcji wprowadziliśmy do naszej aplikacji wykorzystanie mechanizmu sesji użytkownika.
Używając narzędzi deweloperskich w przeglądarce, znajdź identyfikator sesji użytkownika swojej aplikacji - W bazie danych powinny się znajdować dane powiązane z sesją użytkownia postaraj się je odnaleźć i odczytać.
- Spróbuj zmodyfikować identyfikator sesji i zobacz, co się stanie w takim przypadku.
- Poszukaj w dokumentacji Django informacji, jak możesz dodać swoje własne cookies do żądań obsługiwanych przez aplikację i jak możesz je odczytać