r08-05.doc

(2574 KB) Pobierz
8

 

 

W tym rozdziale:

·         Uwierzytelnienie poprzez HTTP

·         Uwierzytelnienie w oparciu o formularz

·         Uwierzytelnienie niestandardowe

·         Certyfikaty cyfrowe

·         Protokół bezpiecznej transmisji danych(SSL)

 

Rozdział 8.

Bezpieczeństwo

Bezpieczeństwo danych jest jednym z najważniejszych zagadnień programowania sieciowego, ponieważ coraz więcej firm decyduje się na obsługę klientów za pośrednictwem sieci i ich Intranety zapełniane są tajnymi danymi. Bezpieczeństwo danych zajmuje się ochroną tajnych informacji przed niepowołanymi użytkownikami. W przypadku aplikacji sieciowych bezpieczeństwo rozpatrujemy jako:

·         Uwierzytelnienie — możliwość weryfikacji tożsamości biorących udział w wymianie informacji.

·         Autoryzacja — udostępnienie zasobów systemu wybranej grupie użytkowników lub programów.

·         Poufność — pewność, że przesyłane dane mogą odczytać tylko przez nas określeni użytkownicy.

·         Integralność — możliwość sprawdzenia, czy informacja nie została zmieniona podczas transmisji.

Wymienione powyżej aspekty bezpieczeństwa można zilustrować prostym przykładem: klient chce być pewien, że jest połączony z właściwym serwerem (uwierzytelnienie) i żadna informacja (np. numer karty kredytowej) nie zostanie przechwycona (poufność). Firma, sprzedając usługę lub przesyłając tajne informacje poprzez sieć do pracowników, chce zabezpieczyć dane przed nieuprawnionymi użytkownikami. Ponadto obie strony (klient firmy i jej pracownik) chcą mieć pewność, że otrzymali niezmienioną informację.

Wiarygodność, autoryzacja, poufność i integralność są zapewniane przy użyciu technologii certyfikatów cyfrowych. Certyfikaty cyfrowe pozwalają serwerom i klientom na używanie zaawansowanych technik kryptograficznych do zapewnienia identyfikacji i kodowania protekcyjnego. Java zawiera wbudowany zestaw narzędzi wspomagających użycie certyfikatów cyfrowych, co sprawia, że serwlety są wspaniałą platformą do obsługi bezpiecznych aplikacji sieciowych używających technologii certyfikatów cyfrowych.

Bezpieczeństwo powinno również zapewnić ochronę danych przechowywanych na serwerze przed krakerami. Java od podstaw została zaprojektowana jako bezpieczny, sieciowo-zorientowany język, w którym istnieje możliwość wykorzystania komponentów gwarantujących bezpieczeństwo własnych i obcych danych na serwerze.

Niniejszy rozdział przedstawia podstawy zabezpieczania danych w sieci i użycia technik certyfikatów cyfrowych w serwletach. Opisano tu również sposoby zabezpieczania serwera podczas uruchamiania serwletów pochodzących z niepewnych źródeł. W stosunku do poprzednich rozdziałów przedstawiono tu mniej przykładów, gdyż podejmuje on problem wyższego poziomu, a wiele poruszonych tu tematów wymaga implementacji obsługi serwera. Serwlety potraktowano tu jako dodatki.

Autorzy niniejszej książki nie biorą odpowiedzialności za problemy związane z bezpieczeństwem danych, które mogą pojawić się po zastosowaniu rad tu zawartych. Szerszy opis dotyczący problematyki bezpieczeństwa w sieci można znaleźć w książce Web Security & Commerce napisanej przez Simsona Garfinkela i Gene Spafford (O'Reilly). Oczywiście, oni także nie wezmą na siebie odpowiedzialności.

Uwierzytelnienie poprzez HTTP

W rozdziale 4, „Odczytywanie informacji” wspomniano, że protokół HTTP posiada wbudowaną obsługę uwierzytelnienia — zwaną uwierzytelnieniem podstawowym, która jest oparta o prosty model wezwanie-odpowiedź, nazwa_użytkownika-hasło. Korzystając z tej techniki serwer zarządza bazą danych zawierającą nazwy użytkowników i hasła dostępu oraz identyfikuje określone zasoby (pliki, katalogi, serwlety, itp.) jako chronione. Gdy użytkownik zażąda dostępu do zastrzeżonych zasobów, serwer odpowie żądaniem podania nazwy użytkownika i hasła. W tym momencie przeglądarka zwykle wyświetla okno dialogowe zawierające pola, w które użytkownik wpisuje swoją nazwę i hasło, po czym dane te przesyłane są do serwera. Jeśli przesłane serwerowi informacje istnieją w jego bazie danych, dostęp do zasobów chronionych jest przyznawany. Cały opisany tu proces uwierzytelnienia odbywa się na serwerze.

Uwierzytelnienie podstawowe nie zapewnia poufności i integralności, co sprawia, że jest to słaby system zabezpieczenia dostępu do danych. Problem polega na tym, że przesyłane poprzez sieć hasła są kodowane za pomocą powszechnie znanej metody Base64. Każdy monitorując strumień danych TCP/IP ma pełny i natychmiastowy dostęp do przesyłanej informacji łącznie z nazwą użytkownika i hasłem chyba, że zostanie wykorzystana dodatkowa metoda kryptografii SSL (omówiona w dalszej części rozdziału). Ponadto hasła są przechowywane na serwerze w postaci czystego tekstu czyniąc je łatwym łupem dla każdego, kto włamie się do systemu plików serwera. Strony zabezpieczone opisaną tu metodą nie mogą zostać uznane za bezpieczne.

Uwierzytelnienie szyfrowane (typu digest) bazuje na schemacie omówionej metody, ale poprzez sieć przesyłany jest łańcuch tworzony za pomocą algorytmu szyfrowania MD5 z nazwy użytkownika, hasła, URI, metody żądania HTTP i losowo generowanej przez serwer wartości jednokrotnego użycia (nounce). Obie strony transakcji (klient i serwer) znają hasło i na jego podstawie generują szyfr. Dostęp do danych jest dozwolony, jeśli łańcuchy klienta i serwera są zgodne. Transakcje zabezpieczone w ten sposób są bezpieczniejsze, gdyż łańcuch jest ważny tylko dla jednego żądania i jednej wartości jednokrotnego użycia (nonce value). Niestety, tak jak w poprzedniej metodzie, serwer nadal zawiera bazę danych z oryginalnymi (nie zaszyfrowanymi) hasłami. Poza tym niewiele przeglądarek obsługuje tą technikę.

Podsumowując można stwierdzić, że uwierzytelnianie za pomocą protokołu HTTP jest użyteczne w środowiskach wymagających niskiego poziomu bezpieczeństwa danych. Dobrym przykładem takiego środowiska jest płatna witryna gazety internetowej, gdzie twórcom bardziej zależy na dostępności serwisu niż na ścisłym bezpieczeństwie danych. W tym przypadku uwierzytelnianie za pomocą HTTP jest wystarczającą metodą.

Konfiguracja uwierzytelnienia HTTP

W starszych interfejsach Servlet API (wersje wcześniejsze niż 2.2) sposób konfiguracji uwierzytelnienia różnił się w zależności od typu serwera. Począwszy od wersji 2.2 sposób uwierzytelniania został znormalizowany. W pliku opisu rozmieszczenia* web.xml określa się metodę zabezpieczenia danych. Za pomocą tego pliku można stosować wybraną metodę zabezpieczenia w różnych serwerach.

Instalacja mechanizmu zabezpieczeń dla interfejsu Servlet API 2.2 na serwerze nie jest konieczna, aby zapewnić kompatybilność z tym interfejsem. Zalecana jest implementacja pełnego systemu bezpieczeństwa, ale kontener serwletu może użyć tylko część mechanizmu bezpieczeństwa lub nie użyć go wcale. Implementacja pełnego mechanizmu bezpieczeństwa na serwerze jest wymagana tylko wtedy, gdy chcemy, aby serwer był kompatybilny z bardziej zaawansowaną platformą Java 2 (Enterprise Edition — J2EE),w której Servlet API 2.2 stanowi tylko część systemu. Poza tym, bezpieczeństwo jest jednym z najnowszych i nie do końca poznanych aspektów interfejsu Servlet API 2.2, co sprawia, że serwery sieciowe mogą różnić się pod względem implementacji mechanizmów bezpieczeństwa. Aby mieć pewność, że witryna pozostanie bezpieczna, należy ją co najmniej raz przetestować podczas przenoszenia danych pomiędzy serwerami.

Uwierzytelnienie w oparciu o rolę

Za pomocą znaczników (umieszczonych w pliku rozmieszczenia aplikacji sieciowej) można określić strony, do których dostęp mają tylko użytkownicy posiadający specjalne uprawnienia. W ten sposób zapewniamy dostęp wszystkim użytkownikom do witryny, ale niektóre strony zawarte w witrynie możemy udostępniać przez nas wybranym użytkownikom. Jednym ze sposobów zapewnienia ograniczonego dostępu jest skorzystanie z metody uwierzytelnienia opartego o rolę. W tej metodzie, zezwolenia dostępu są przyznawane abstrakcyjnemu podmiotowi security role, a dostęp do danych mają tylko użytkownicy (lub grupy użytkowników) należący do określonej roli. Można, na przykład, utworzyć witrynę w ten sposób, aby strony zawierające informacje o wynagrodzeniach były dostępne tylko użytkownikom w roli manager. W pliku opisu rozmieszczenia określany jest tylko sposób dostępu do danych w zależności od roli użytkownika, a konkretne przypisanie ról użytkownikom (lub grupom użytkowników) ma miejsce podczas wdrażania aplikacji, przy pomocy narzędzi serwerowych. To przypisanie można realizować w oparciu o informacje zawarte w plikach tekstowych, tabelach baz danych, systemie operacyjnym, itd.

Ograniczanie dostępu do serwletu

Załóżmy, że chcemy zapewnić ograniczony dostęp do serwletu, co pokazano w przykładzie 8.1. (Za pomocą omawianej metody można zabezpieczać nie tylko serwlety, ale również pliki i inne aplikacje).

Przykład 8.1.

Czy jesteś pewien, że masz pozwolenie na czytanie tego przykładu?

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class SalaryServer extends HttpServlet {

public void doGet (HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType ("text/plain");

PrintWriter out = res.getWriter();

out.println("Informacja ściśle tajna:"); out.println("Każdy zarabia więcej od Ciebie!");

       }

}

 

Załóżmy, że posiadamy bazę danych użytkowników naszego serwera, zawierającą listę identyfikatorów, haseł i ról. Nie wszystkie serwery obsługują taką bazę danych, a te, które to robią, implementują ją w dowolny sposób. W przypadku serwera Tomcat 3.2 dane użytkowników są umieszczane w pliku conf/tomcat-users.xml, pokazanym w przykładzie 8.2. Kolejne wersje Tomcata pozwolą na bezpieczniejszy zapis informacji (nasza wersja umieszcza dane w postaci niezaszyfrowanej).

Przykład 8.2.

Plik conf/tomcat-users.xml

<tomcat-users>

     <user name="Dilbert"     password="dnrc"          roles="engineer" />

     <user name="Willy"       password="iluvalice"     roles="engineer,slacker" />

     <user name="MrPointyHair" password="MrPointyHair"  roles="manager,slacker" />

</tomcat-users>

Należy zauważyć, że tylko MrPointyHair jest w roli manager. Zakładając, że potrafi podłączyć swój komputer do sieci, powinien być jedyną osobą mającą dostęp do naszego tajnego serwletu. Określimy to w pliku web.xml, pokazanym w przykładzie 8.3.

Zwróćmy uwagę na to, że istotne znaczenie ma kolejność wystąpienia znaczników w pliku web.xml. Należy zawsze stosować znaczniki w następującym porządku: <security-constraint>, <login-config>, a następnie <security-role>. Nie bez znaczenia jest również kolejność występowania elementów wewnątrz tych znaczników. Tworząc pliki web.xml najłatwiej skorzystać z pomocy narzędzi graficznych, które automatycznie „zadbają” o odpowiednią strukturę pliku.

Przykład 8.3.

Ograniczenie dostępu za pomocą uwierzytelniania podstawowego.

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"

"http://java.sun.com/j2ee/dtds/web_app_2_2.dtd">

<web-app>

<servlet>

<servlet_name>

secret

</servlet-name>

<servlet-class>

SalaryServer

</servlet-class>

</servlet>

 

<security-constraint>

<web-resource-collection>

<web-resource-name>

SecretProtection

</web-resource-name>

<url-pattern>

/servlet/SalaryServer

</url-pattern>

    <url-pattern>

/servlet/secret

</url-pattern>

<http-method>

GET

</http-method>

<http-method>

POST

</http-method>

    </web-resource-collection>

    <auth-constraint>

    <role-name>

    manager

    </role-name>

    </auth-constraint>

</security-constraint>

  <login-config>

<auth-method>

BASIC  <!-- BASIC, DIGEST, FORM, CLIENT-CERT -->

</auth-method>

<realm-name>

         Default       <!-- Opcjonalne, użyteczne tylko dla BASIC -->

         </realm-name>

</login-config>

<security-role>

    <role-name>

      manager

</role-name>

</security-role>

</web-app>

Za pomocą tego pliku opisu rozmieszczenia zabezpieczono wszystkie metody GET i POST, które udostępniają /servlet/secret i /servlet/SalaryServer użytkownikom w roli manager, zalogowanym za pomocą uwierzytelnienia podstawowego. Pozostała część witryny jest dostępna bez ograniczeń.

Znacznik <security-constraint> chroni dostęp do danych, których ścieżki dostępu znajdują się wewnątrz znacznika <web-resource-collection> w ten sposób, że dostęp jest zapewniony tylko użytkownikom, których role umieszczono wewnątrz znacznika <auth-constraint>. Każdy znacznik <web-resource-collection> zawiera nazwę, chronione adresy URL oraz kilka metod HTTP o ograniczonym dostępie. Dla wzorców adresów URL można użyć identycznych symboli wieloznacznych, jakich używa się do mapowania serwletu — co opisano w rozdziale 2 „Podstawy serwletu HTTP”. Powinno się wyznaczyć metody chronione, przynajmniej GET i POST. Jeśli nie wpiszemy znacznika <http-method>, to wszystkie metody zostaną uznane za chronione. Znacznik <auth-constraint> zawiera nazwy ról użytkowników, którym udostępnia się zbiór zasobów.

Znacznik <login-config> określa sposób logowania się, którego używa aplikacja. W tym przypadku określamy podstawowe uwierzytelnienie za pomocą dyrektywy Default. W znaczniku <auth-method> umieszcza się wartości BASIC, DIGEST, FORM i CLIENT-CERT, które odpowiadają typom uwierzytelnienia: podstawowemu, szyfrowanemu, w oparciu o formularz i przy użyciu certyfikatów klienckich. O uwierzytelnieniach w oparciu o formularz i przy użyciu certyfikatów klienckich będzie mowa w dalszej części rozdziału. Znacznik <realm-name> określa zakres użycia loginu (tu zawarta jest informacja określająca uprawnienia poszczególnych ról). Ten znacznik jest używany tylko w uwierzytelnieniu podstawowym i szyfrowanym.

Znacznik <security-role> zawiera listę ról, która może być użyta przez aplikację.

W omówionej metodzie nie ma możliwości udostępniania zasobów wszystkim z pominięciem użytkowników z „czarnej listy”. Najprostszym wyjściem z tej sytuacji może być utworzenie grupy użytkowników nie należących do „czarnej listy”.

Teraz, gdy plik web.xml jest już gotowy, wszystkie żądania dostępu do chronionych danych zostaną przechwycone przez serwer i dane uwierzytelniające użytkownika zostaną sprawdzone. Dostęp jest przydzielany, jeśli uprawnienia są ważne, a serwer przypisuje użytkownikowi rolę manager. W przeciwnym razie dostęp jest zabroniony, a okno dialogowe przeglądarki poprosi użytkownika o kolejną próbę zalogowania się.

Wyszukiwanie informacji uwierzytelnienia

Serwlet może odczytać informację na temat uwierzytelnienia dzięki dwóm metodom przedstawionym w rozdziale 4: getRemoteUser() i getAuthType(). Serwlet API 2.2 zawiera dodatkową metodę getUserPrincipal(), która zwraca obiekt implementujący interfejs java.security.Principal

public java.security.Principal HttpServletRequest.getUserPrincipal()

Principal to termin techniczny określający uwierzytelniany podmiot. Może nim być użytkownik, grupa, korporacja lub po prostu identyfikator. Interfejs Principal zawiera metodę getName() zwracającą nazwę podmiotu. Metoda getUserPrincipal() służy do określenia uwierzytelnionej tożsamości użytkownika, podczas gdy getRemoteUser()do zapewnienia kompatybilności ze skryptami CGI. Metoda IsUserInRole() została także wprowadzona w Servlet API 2.2. Ta metoda zwraca wartość true, gdy użytkownik należy do określonej roli:

public boolean HttpServletRequest.isUserInRole(String role)

Ta metoda pozwala na wykonanie pewnych decyzji wewnątrz serwletu. Załóżmy, że deskryptor rozmieszczenia pozwala uzyskać dostęp wielu różnym rolom. Wywołanie tej metody pozwala serwletowi na zróżnicowanie dostępu do danych zależności od uwierzytelnionej roli użytkownika.

W deskryptorze rozmieszczeń (pliku opisu rozmieszczenia) można utworzyć pseudonimy, (można sprawić, by zasięg roli mgr będzie taki sam, jak zasięg roli manager). Ma to zastosowanie podczas integracji serwletów pochodzących z innych aplikacji sieciowych, które używają innych nazw ról niż nasz serwlet. Pseudonimy są konfigurowane osobno dla każdego serwletu, za pomocą znacznika <security-role-ref> wewnątrz znacznika <servlet>, jak pokazano w poniższym fragmencie web.xml:

<servlet>

<servlet-name>

secret

</servlet-name>

<servlet-class>

SalaryViewer

</servlet-class>

<security-role-ref>

<role-name>

          mgr          <!-- Nazwa używana przez serwlet -->

          </role-name>

          <role-link>

              manager       <!-- Nazwa używana przez deskryptor rozmieszczenia -->

          </role-link>

</security-role-ref>

</servlet>

 

Może istnieć dowolna ilość znaczników <security-role-ref>. Należy pamiętać, że dane pseudonimy są ważne tylko podczas dostępu do serwletu poprzez jego zarejestrowaną nazwę.

W przykładzie 8.4 serwlet wyświetla klientowi jego nazwę, podmiot, rodzaj uwierzytelnienia (BASIC, DIGEST, ...

Zgłoś jeśli naruszono regulamin