r07-05.doc

(376 KB) Pobierz
Szablon dla tlumaczy

 

W tym rozdziale:

·         Uwierzytelnianie użytkownika

·         Ukryte pola danych formularza

·         Trwałe cookies

·         Śledzenie sesji API

Rozdział 7.

Śledzenie sesji

HTTP jest protokołem bezstanowym: nie ma wbudowanej funkcji, która umożliwiałaby serwerowi rozpoznawanie, że cała sekwencja pochodzi w całości od tego samego użytkownika. Zwolennicy poszanowania prywatności mogą postrzegać taką sytuację pozytywnie, jednakże większość programistów WWW uważa to za wielki kłopot, jako że aplikacje WWW nie są bezstanowe. Rozbudowane aplikacje WWW muszą współdziałać z klientem zarówno wstecznie jak i z wyprzedzeniem, zapamiętując informacje o nim pomiędzy zleceniami. Klasycznym przykładem może tutaj być aplikacja koszyka zakupów — klient musi mieć możliwość wkładania artykułów do swojego wirtualnego koszyka, a serwer musi zapamiętać te artykuły, aż do czasu kiedy klient wyloguje się kilka stron zleceniowych później (często trwa to nawet kilka dni!).

Problem stanu HTTP można najlepiej zrozumieć wyobrażając sobie szerokie forum dyskusji, na którym jesteśmy gościem honorowym. Następnie wyobraźmy sobie, na tym forum, dziesiątki internautów rozmawiających z nami w tym samym czasie. Wszyscy zadają nam pytania, odpowiadając na nasze, powodując że klepiemy w klawiaturę jak opętali usiłując odpowiedzieć wszystkim. Teraz wyobraźmy sobie, z kolei, że kiedy ktoś do nas pisze forum dyskusyjne nie podaje jego tożsamości, jedyne co widzimy to mieszanina pytań i odpowiedzi. W forum tego typu najlepsze co możemy zrobić to prowadzenie prostych konwersacji, np. poprzez odpowiadanie na bezpośrednie pytania. Jeżeli będziemy próbowali zrobić coś więcej, np. zadać swoje pytanie, może okazać się, że nie będziemy wiedzieli w którym momencie nadejdzie odpowiedź. Na tym właśnie polega problem stanu HTTP. Serwer HTTP „widzi” tylko serię zleceń, potrzebuje więc dodatkowej pomocy aby dowiedzieć się kto właściwie składa zlecenie.*

Rozwiązaniem, jak już może się zdążyliśmy domyśleć, jest przedstawienie się klienta w momencie składania każdego zlecenia. Każdy klient musi dostarczyć niepowtarzalny identyfikator, który umożliwi serwerowi jego identyfikację, mogą to być również informacje, które pozwolą serwerowi poprawnie obsłużyć zlecenie. Wykorzystując przykład forum dyskusji, każdy uczestnik musi rozpocząć swoje każdą wypowiedź od czegoś na wzór: „Cześć, Mam na imię Jason i...” lub „Cześć, właśnie zapytałem o twój wiek i...”. Jak się przekonamy podczas lektury tego rozdziału, istnieje wiele sposobów umożliwiających klientom „HTTP-owym” na przesłanie wstępnych informacji wraz z każdym zleceniem.

Pierwsza połowa tego rozdziału poświęcona będzie tradycyjnym technikom śledzenia sesji, wykorzystywanych przez programistów CGI: uwierzytelnianie użytkownika, ukryte pola danych formularza, przepisywanie URL-u oraz trwałe cookies. Natomiast druga połowa rozdziału omawia wbudowaną obsługę dla śledzenia sesji w Interfejsie API. Obsługa ta jest zbudowana na szczycie tradycyjnych technik i znacznie upraszcza zadanie śledzenia sesji w naszych apletach. Wszystkie omówienia zawarte w tym rozdziale opierają się na założeniu, że używany jest serwer pojedynczy. Rozdział 12 „Aplety Przedsiębiorstw i J2EE

Tytuł rozdziału 12

” wyjaśnia jak jest realizowana obsługa stanu wspólnej sesji w serwerach wielo-wejściowych.

Uwierzytelnianie użytkownika

Jednym ze sposobów przeprowadzenia śledzenia sesji jest wykorzystanie informacji pochodzącej z uwierzytelniania użytkownika. Uwierzytelnianie użytkownika zostało omówione w rozdziale 4 „Odczytywanie Informacji”, jednak w celu przypomnienia, uwierzytelnianie użytkownika stosowane jest kiedy serwer WWW ogranicza dostęp do niektórych ze swoich zasobów tylko do tych klientów, którzy logują się przy użyciu określonego hasła i nazwy użytkownika. Po zalogowaniu się klienta, nazwa użytkownika jest dostępna dla apletu poprzez metodę getRemoteUser().

Możemy wykorzystać nazwę użytkownika do śledzenia sesji klienta. Kiedy użytkownik już się zaloguje, przeglądarka pamięta jego nazwę i odsyła ją wraz z hasłem kiedy użytkownik przegląda kolejne strony witryny WWW. aplet może zidentyfikować użytkownika wykorzystując jego nazwę, tym samym może również wyśledzić jego sesję. Dla przykładu kiedy użytkownik doda coś do swojego wirtualnego koszyka na zakupy, fakt ten może zostać zapamiętany (we wspólnej klasie lub np. w zewnętrznej bazie danych) i wykorzystany w przyszłości przez inny aplet — kiedy użytkownik wejdzie na stronę końcową.

Aplet, który wykorzystuje uwierzytelnianie użytkownika, mógłby dla przykładu dodać artykuł do koszyka użytkownika, przy pomocy następującego kodu:

 

String name = req.getRemoteUser();

if (name == null) {

   // Wyjaśnij, że administrator serwera powinien chronić tą stronę

}

else (

   String[] items = req.getParameterValues("artykuł");

   if (items != null) {

      for (int i = 0; i < items.length; i++) (

      addItemToCart(name, items[i]) ;

     }

   }

}

 

Inny aplet, może w przyszłości odczytać artykuły z koszyka zakupów za pomocą następującego kodu:

 

String name = req.getRemoteUser();

if (name == null) {

   // Wyjaśnij, że administrator serwera powinien chronić tą stronę

}

else (

  String[] items = getItemsFromCart(name);

}

 

Największą zaletą wykorzystywania uwierzytelniania użytkownika w celu przeprowadzania śledzenia sesji jest prosta implementacja. Po prostu mówimy serwerowi, żeby chronił określony zestaw stron (zgodnie z instrukcjami z rozdziału 8 „Bezpieczeństwo”), a następnie stosujemy metodę getRemoteUser() w celu identyfikacji każdego klienta. Kolejna zaleta jest taka, iż technika ta działa nawet kiedy użytkownik wchodzi na naszą stronę z różnych komputerów. Technika ta działa również kiedy użytkownik nie łączy się z naszą witryną lub kiedy zamyka przeglądarkę przed powrotem na naszą stronę.

 

Największym mankamentem uwierzytelniania użytkownika jest fakt, iż każdy użytkownik musi zarejestrować się na konto, a następnie logować się za każdym razem kiedy odwiedza naszą witrynę. Większość użytkowników toleruje rejestrowanie i logowanie się jako zło konieczne kiedy uzyskują dostęp do informacji poufnych, jednakże w przypadku zwykłego śledzenia sesji jest to prawdziwa uciążliwość. Kolejna uciążliwość to fakt, że podstawowe uwierzytelnianie HTTP nie zapewnia żadnego mechanizmu wylogującego; użytkownik musi zamknąć swoją przeglądarkę żeby się wylogować. Problemy, w przypadku uwierzytelniania użytkownika stwarza niemożność utrzymania przez użytkownika więcej niż jednej sesji na tej samej stronie (witrynie) WWW. Wniosek z powyższych rozważań nasuwa się sam: koniecznie potrzebujemy innych rozwiązań dla obsługi śledzenia anonimowego konta i dla obsługi uwierzytelnionego śledzenia sesji z wylogowaniem.

 

Ukryte pola danych formularza

Jednym ze sposobów obsługi śledzenia anonimowego konta jest zastosowanie ukrytych pól danych formularza. Jak sama nazwa wskazuje, są to pola dodane do formularza HTML, które nie są wyświetlane w przeglądarce klienta. Pola te są odsyłane do serwera, kiedy formularz zawierający je, zostaje przedłożony. Ukryte pola danych formularza dołączamy przy pomocy HTML-u podobnego do poniższego:

 

<FORM ACTION="/ servlet /MovieFinder" METHOD="POST">

...                              

<INPUT TYPE=hidden NAME="zip" VALUE=" 94040 ">

<INPUT TYPE=hidden MAME="poziom" VALUE="expert">

...

</FORM>

 

W pewnym sensie ukryte pola danych formularza definiują zmienne stałe dla formularza. Dla apletu otrzymującego przedłożony formularz nie ma żadnej różnicy pomiędzy ukrytym polem danych a polem, które jest widoczne.

 

Przy zastosowaniu ukrytych pól danych formularzowych, możemy przepisać nasze aplety koszyka zakupów, tak że użytkownicy będą mogli dokonywać zakupów anonimowo, do czasu zakończenia. Na przykładzie 7.1 została zaprezentowana technika wykorzystująca aplet, który wyświetla zawartość koszyka zakupów użytkownika i pozwala użytkownikowi na dodanie większej liczby artykułów lub na zakończenie zakupów. Przykładowy widok ekranu został przedstawiony na rysunku 7.1.

 

Przykład 7.1.

Śledzenie sesji przy użyciu ukrytych pól danych formularza

 

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class ShoppingCartViewerHidden extends HttpServlet {

 

public void doGet(HttpServletReguest req, HttpServletResponse res)

                     throws ServletException, IOException {

   res. setContentType (" tekst/html") ;

   PrintWriter out = res.getWriter();

  

   out.println("<HEAD><TITLE>Current Shopping Cart  

   Items</TITLE></HEAD>");

   out.println("<BODY>") ;

 

   // Artykuły z koszyka przekazywane są jako parametr artykułu.

   String [ ] items = req.getParameterValues ("artykuł") ;

 

   // Drukuj bieżące artykuły z koszyka.

   out.println("Aktualnie w swoim koszyku masz następujące artykuły   

                  cart:<BR>");

   if (items == null) {

      out.println("<B>None</B>") ;

   }

   else (

      out.println("<UL>") ;

      for (int i = 0; i < items.length; i++) {

         out.println("<LI>" + items[i]);

}

out.println"</UL>") ;

}

 

   // Spytaj czy użytkownik chce jeszcze dodać jakieś artykuły, czy

                    też // chce zakończyć.

   // Dołącz bieżące artykuły jako ukryte pola danych, tak żeby mogły

                    być // przekazane.

   out.println("<FORM ACTION=\"/servlet/ShoppingCart\" METHOD=POST>");

   if (items != null) {

      for (int i = 0; i < items.length; i++) {

         out.println("<INPUT TYPE=HIDDEN NAME="artykuł" VALUE=\"" +

            items[i] + "\">");

   }

}

out.println("Chciałbyś<BR>");

out.println("<INPOT TYPE=SUBMIT VALUE=\" Dodaj Więcej Artykułów \">");

out.println("<INPUT TYPE=SUBMIT VALUE=\" Zakończ \">");

out.println("</FORM>")

 

out.println("</BODY></HTML>");

 

}

}

Rysunek 7.1.

Zawartość koszyka na zakupy

 

Aplet zaczyna od czytania artykułów, które już znajdują się w koszyku, używając getParameterValues("item"). Przypuszczalnie wartości parametrów artykułów zostały przesłane do tego apletu przy wykorzystaniu ukrytych pól danych. Następnie aplet wyświetla użytkownikowi bieżące artykuły i pyta go czy chce jeszcze jakieś dodać, czy też chce już zakończyć zakupy. Pytania zadawane są za pomocą formularza zawierającego ukryte pola danych, tak więc adresat formularza (aplet ShoppingCart) otrzymuje aktualne artykuły jako część przedłożenia.

 

W sytuacji, w której sesji klienta towarzyszy coraz więcej informacji, przekazywanie ich za pomocą ukrytych pól danych formularza może okazać się uciążliwe. W takich sytuacjach, można przesłać tylko jedną, określoną identyfikację (ID) sesji, która umożliwi zidentyfikowanie określonej (niepowtarzalnej) sesji klienta. ID sesji może być częścią kompletnych informacji o sesji, które są przechowywane na serwerze.

 

Zwróćmy uwagę, iż identyfikacje sesji muszą być przechowywane jako informacje tajne serwera, ponieważ klient znający ID sesji innego klienta mógłby, używając sfałszowanego ukrytego pola danych formularza, przyjąć drugą tożsamość. W konsekwencji identyfikacje sesji powinny być generowane w taki sposób, żeby nie dało się ich łatwo odgadnąć lub sfałszować, a bieżące ID sesji powinny być chronione, tak więc dla przykładu, nie upubliczniajmy loginu dostępu serwera, ponieważ zarejestrowane URL-e mogą zawierać identyfikacje sesji dla formularzy, przedłożonych za pomocą zleceń GET.

 

Ukryte pola danych formularza mogą zostać wykorzystane do wdrożenia uwierzytelniania bez wylogowania się. Prezentujemy po prostu formularz HTML jako ekran logujący, kiedy użytkownik został już raz uwierzytelniony przez serwer, jego tożsamość może zostać powiązana z jego określonym ID sesji. Przy wylogowywaniu się ID sesji może zostać usunięte (poprzez nie przesyłanie do klienta późniejszych formularzy) lub po prostu nie zapamiętane. Rozwiązanie to zostało szerzej omówione w rozdziale 8.

 

Zaletą ukrytych pól danych formularza jest ich powszechność oraz obsługa „anonimowości”. Ukryte pola danych są obsługiwane we wszystkich popularnych przeglądarkach, ponadto mogą być wykorzystywane przez klientów, którzy się nie zarejestrowali lub nie zalogowali. Główną, z kolei, wadą tej techniki jest fakt, że sesja utrzymuje się tylko poprzez sekwencję dynamicznie generowanych formularzy. Sesja nie może być utrzymana za pomocą dokumentów statycznych, dokumentów przesłanych jako e-mail, dokumentów z których utworzono zakładki czy za pomocą zamknięć przeglądarki.

 

Przepisywanie URL-u

Przepisywanie URL-u jest kolejnym sposobem na obsługę śledzenia anonimowego konta. W przypadku stosowania przepisywania URL-u, każdy lokalny URL, na który kliknie użytkownik, jest dynamicznie modyfikowany lub przepisywany — w celu dołączenia dodatkowych informacji. Te dodatkowe informacje mogą być w formie informacji dodatkowej ścieżki, w formie parametrów dodanych lub w formie jakiejś własnej, serwerowo — specyficznej zmiany URL-u. Z powodu ograniczonej przestrzeni dostępnej dla przepisywania URL, dodatkowe informacje są zwykle ograniczane do ID określonej sesji. Dla przykładu, poniższe URL-e zostały przepisane aby przekazać identyfikację sesji 123:*

 

http://server:port/servlet/Rewritten                 original

http://server:port/servlet/Rewritten/123             extra path infonnation

http://server:port/servlet/Rewritten?sessionid=123   added parameter

http://server:port/servlet/Rewritten;jsessionid=123  custom change

 

Każda technika przepisywania ma swoje złe i dobre strony. Informacja dodatkowej ścieżki działa poprawnie na wszystkich serwerach, z wyjątkiem sytuacji kiedy serwer musi użyć tą informację jako informację prawdziwej ścieżki. Zastosowanie parametru dodanego również sprawdza się na wszystkich serwerach, jednak może ono powodować konflikt nazw parametrów. Standardowa, serwerowo-specyficzna zmiana działa we wszystkich warunkach dla apletów, które obsługują tą zmianę.

 

Przykład 7.2 prezentuje poprawioną wersję naszego „shopping cart viewer”, który wykorzystuje przepisywanie URL-u (w formie informacji dodatkowej ścieżki) w celu anonimowego śledzenia koszyka zakupów.

 

Przykład 7.2.

Śledzenie sesji za pomocą przepisywania URL-u

 

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class ShoppingCartViewerRewrite extends HttpServlet {

 

  public void doGet(HttpServletRequest req, HttpServletResponse res)

                      throws ServletException, IQException {

      res.setContentType (" tekst/html") ;

      PrintWriter out = res.getWriter();

 

      out.println("<HEAD><TITLE>Current Shopping Cart   

         Items</TITLE></HEAD>");

      out.println("<BODY>");

 

      // Pobierz bieżące ID sesji lub, w razie konieczności, wygeneruj

      String sessionid = req.getPathInfo();

      if (sessionid == null) {

      sessionid = generateSessionId();

      }

 

      // Artykuły z koszyka związane są z ID sesji

      String[] items = getItemsFromCart(sessionid);

 

      // Drukuj bieżące artykuły z koszyka.

      out.println("Aktualnie masz w swoim koszyku nastepujące  

              artykuły:<BR>");

      if (items == null) {

      out.println("<B>None</B>") ;

      }

      else {

     out.println("<UL>") ;

for (int i = 0; i < items.length; i++) {

   out.println("<LI>" + items[i]) ;

}

out.println("</UL>") ;

}

 

// Spytaj użytkownika czy chce jeszcze dodać jakieś artykuły, czy też // zakończyć.

// Dołącz ID sesji do URL-u operacyjnego.

out.println("<FORM ACTION=\"/servlet/ShoppingCart/" + sessionid+

"\" METHOD=POST>") ;

out.println("Czy chcesz<BR>");

out.println("<INPUT TYPE=SUHMIT VALUE=\" Dodać Więcej Artykułów \">"),

out.println("<INPUT TYPE=SUBMIT VALUE=\" Zakończyć \">");

out.println("</FORM>") ;

 

// zaproponuj stronę pomocy. Dołącz ID sesji do URL-u.

out. println ("W celu uzyskania pomocy kliknij <A

                   HREF=\"/servlet/Help/" + sessionid+

     "?topic=ShoppingCartViewerRewrite\">here</A>");

   out.println("</BODY></HTML>") ;

}

 

private static String generateSessionId() {

   String uid = new java.rmi.server. UID().toStringO; // gwarantowana 

                niepowtarzalność

   return Java.net.URLEncoder.encode(uid); // zakoduj wszystkie 

                specjalne // znaki

}

 

private static String[] getItemsFromCart(String sessionid) {

   // Nie wdrożone

   }

}

 

Pierwszą rzeczą wykonywaną przez ten aplet jest pobranie aktualnej identyfikacji sesji przy użyciu metody IDgetPathInfo(). Jeżeli ID sesji nie jest określone, aplet wywołuje generateSessionId(), aby wygenerować nową, niepowtarzalną identyfikację sesji, wykorzystując w tym celu specjalnie do tego celu zaprojektowaną klasę RMI. ID sesji jest również wykorzystywane do załadowania i wyświetlenia aktualnych artykułów z koszyka. Identyfikacja jest później dodawana do atrybutu formularza ACTION, tak że może on zostać odczytany przez aplet ShoppingCart. Identyfikacja sesji jest również dodawana do URL-u pomocy, który wywołuje aplet Help. Coś takiego nie było możliwe przy ukrytych polach danych formularza, ponieważ aplet Help nie jest adresatem przedłożenia formularza.

Złe i dobre strony przepisywania URL-u ściśle pokrywają się z dobrymi i złymi stronami ukrytych pól danych formularza. Obie techniki są stosowane we wszystkich typach przeglądarek, pozwalają na dostęp anonimowy obie również mogą zostać użyte do wdrożenia uwierzytelniania z wylogowaniem się. Główna różnica między tymi technikami polega na tym, że przepisywanie URL-u działa dla wszystkich dynamicznie utworzonych dokumentów, takich jak aplet Help, a nie tylko, jak ma to miejsce w przypadku ukrytych pól danych formularza — dla formularzy. Dodatkowo, przy poprawnej obsłudze serwera, własne przepisywanie URL-u może działać, nawet z dokumentami statycznymi. Niestety często przepisywanie URL-u bywa męczące.

Trwałe cookies

Czwarta z kolei technika umożliwiająca śledzenie sesji wiąże się z trwałymi cookies. Cookie (ciasteczko) to bit informacji przesyłany do przeglądarki przez serwer WWW, który następnie może zostać odczytany z tej przeglądarki. Kiedy przeglądarka otrzymuje cookie, zapisuje go, i następnie odsyła go z powrotem do serwera, za każdym razem kiedy wchodzi na jakąś jego stronę, zgodnie z pewnymi zasadami. Ponieważ wartość cookie może jednoznacznie zidentyfikować klienta, cookies są często używane do śledzenia sesji.

 

Cookies zostały jako pierwsze wprowadzone w Netscape Navigator’ze. Mimo iż nie były one oficjalną częścią specyfikacji HTTP, szybko stały się, de facto, standardem we wszystkich popularnych przeglądarkach, włącznie z Netscape 0.94 Beta (i późniejszych) oraz Microsoft Internet Explorer’ze 2 (i późniejszych). Obecnie grupa robocza Internet Engineering Task Force (IETF) — HTTP Working Group, pracuje nad przekształceniem cookies w oficjalny, napisany w RFC 2109, standard. Więcej informacji o cookies można znaleźć w specyfikacji Netscape'a — Netscape's cookie specification, na stronie ...

Zgłoś jeśli naruszono regulamin