Pokazywanie postów oznaczonych etykietą Bezpieczeństwo. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Bezpieczeństwo. Pokaż wszystkie posty

4 kwietnia 2008

Mechanizm pojedynczego uwierzytelnienia, czyli Single Sign-On

Mechanizm pojedynczego uwierzytelnienia – czyli Single Sign-On, w skrócie SSO – to obszerny temat. Wyobraźmy sobie, że udostępniamy użytkownikom wiele odrębnych aplikacji, z których każda wymaga uwierzytelnienia. Czy nie byłoby dobrze, gdyby dało się używać ich wszystkich podając swój identyfikator i hasło (albo inne dane uwierzytelniające) tylko jednokrotnie? Z pewnością wygodniej niż gdybyśmy mieli to robić dla każdej aplikacji z osobna.

W dzisiejszym artykule opiszę swą wizję SSO dla środowiska złożonego z aplikacji WWW. SSO nie żyje jednak w próżni i same dla siebie, przeciwnie, jest częścią ogólnej architektury bezpieczeństwa dla systemów klasy korporacyjnej (ang. enterprise). Przynajmniej ja tak to widzę i tak też o SSO będę pisał. Zacznijmy od spojrzenia na poniższy diagram ilustrujący proces uwierzytelnienia i pokazujący kluczowe komponenty.




Na szczególną uwagę zasługuje komponent nazwany na powyższym diagramie serwerem bezpieczeństwa. Otóż mój pomysł na implementacje kompleksowego systemu bezpieczeństwa dla przedsiębiorstwa polega na tym, że wszystkie decyzje dotyczące bezpieczeństwa, tj. autoryzacja i uwierzytelnienie podejmowane są przez wyspecjalizowany komponent/serwer, nie przez każdą z aplikacji z osobna. Nie jest więc tak, że na potrzeby autoryzacji aplikacje pobierają listę ról przypisanych do bieżącej tożsamości i konfrontują tą listę z rolami przypisanymi do danego zasobu. Aplikacje wysyłają do serwera bezpieczeństwa zapytanie autoryzacyjne podając identyfikatory zasobów i identyfikator kontekstu a to serwer podejmuje decyzje na podstawie sobie tylko znanych kryteriów. Przede wszystkim, nie wydaje mi się być dobrym pomysłem zaszywanie w aplikacji informacji o związku zasobów z rolami. Ale jest to dyskusja na oddzielny artykuł. Wróćmy do SSO.

Filtr weryfikacji tożsamości to filtr servletowy, który sprawdza czy w żądaniu HTTP zostało przekazane ciasteczko o odpowiedniej nazwie. Jeśli ciasteczko jest dostępne to pobiera z niego identyfikator kontekstu bezpieczeństwa i przekazuje go do aplikacji, np. jako atrybut żądania HTTP (ang. HTTP request). Ewentualnie, filtr może uprzednio wywołać funkcję weryfikacji identyfikatora udostępnianą przez serwer bezpieczeństwa. Aplikacja (a dokładnie jej moduł autoryzacji) wysyła do serwera bezpieczeństwa zapytanie o autoryzację dostępu dla żądanego zasobu i identyfikatora kontekstu. Na podstawie wyniku podejmuje odpowiednie kroki – zwraca żądany zasób lub komunikat błędu. Serwer bezpieczeństwa pobiera z kontekstu dane o uwierzytelnionej tożsamości i przypisanych jej rolach. Zasoby opisane są odpowiednimi identyfikatorami. Jeśli role przypisane do uwierzytelnionej tożsamości zezwalają na uzyskanie dostępu do opisanego zasobu serwer udziela odpowiedzi pozytywnej. Oczywiście, jeśli przekazany do serwera bezpieczeństwa identyfikator kontekstu nie identyfikuje poprawnie żadnego kontekstu zwracany jest komunikat błędu. Serwer bezpieczeństwa może również odpowiedzieć, że użytkownik ma prawo do uzyskania dostępu, ale pod jakimś określonym warunkiem, np. podania poprawnego hasła jednorazowego.

Jeśli filtr weryfikacji tożsamości nie znajdzie w żądaniu odpowiedniego ciasteczka HTTP, to uruchomiona zostaje procedura uwierzytelniania. Rozpoczyna się ona przekierowaniem do aplikacji uwierzytelniającej. Żądania skierowane do tej aplikacji są filtrowane przez kolejny filtr servletowy – filtr uwierzytelniający. To właśnie ten filtr wykonuje główną pracę. Sprawdza on, czy w żądaniu HTTP znajdują się odpowiednie parametry, tj. identyfikator użytkownika i hasło. Jeśli tak, to są one weryfikowane, jeśli nie, to żądanie jest przekazywane do aplikacji uwierzytelniającej – wyświetlana jest w przeglądarce strona z formularzem uwierzytelniania. Po wypełnieniu i wysłaniu formularza żądanie znowu trafia do filtra. Identyfikator użytkownika i hasło są weryfikowane z użyciem serwera bezpieczeństwa. Poprawna weryfikacja powoduje utworzenie na tym serwerze kontekstu (sesji) bezpieczeństwa. Odsyłany jest identyfikator tego kontekstu. Filtr uwierzytelniający przekierowuje z powrotem do oryginalnie żądanego zasobu ustawiając jednocześnie ciasteczko z identyfikatorem utworzonego kontekstu. Od tej pory filtr weryfikacji tożsamości, niezależnie od tego, jaką aplikację zabezpiecza będzie posiadał prawidłowe informacje o uwierzytelnionym użytkowniku. Tym oto sposobem otrzymujemy efekt uwierzytelnienia do pracy z przeglądarką, niezależnie od tego, jaką aplikację w tej przeglądarce aktualnie użytkujemy – oczywiście pod warunkiem, że wszystkie one używają tej samej infrastruktury bezpieczeństwa. Implementacja opisanych tu mechanizmów już wkrótce – oczywiście, jak nie zabraknie czasu i zapału.

9 grudnia 2007

Uwierzytelnianie i autoryzacja w Acegi Security

Zajmuję się ostatnio nieco zabezpieczaniem różnego rodzaju zasobów IT, w szczególności aplikacji WWW i im więcej to robię tym więcej zbiera mi się na krytykę standardów przemysłowych, które do tego służą. Postanowiłem podzielić się moim postrzeganiem tematu, ale zanim to zrobię ugruntuję nieco swą wiedzę i przygotuje podstawę do dyskusji. Mówiąc standardy przemysłowe mam na myśli de-facto-standardy, czyli np. Acegi Security i mechanizmy popularnych serwerów aplikacyjnych, np. BEA WebLogic Server. W dzisiejszym artykule opiszę, jak wygląda architektura uwierzytelniania i autoryzacji w Acegi. Będzie to jednak opis nie konstruktywny w tym sensie, że nie będę opisywał konfiguracji i nie pokażę przykładu – to już zostało zrobione przez Michała Gołackiego w artykule "Acegi - Uwierzytelnianie i Autoryzacja w Springu".

Całość składa się z trzech zasadniczych części: mechanizmu odpowiedzialnego za uwierzytelnienie, który tworzy kontekst bezpieczeństwa, mechanizmu, który przechowuje ten kontekst oraz mechanizmu autoryzacji, który w trakcie działania aplikacji z tego kontekstu korzysta. Zacznijmy od mechanizmu przechowującego kontekst i tego, co to jest ten kontekst.

Klasą, która przechowuje kontekst bezpieczeństwa jest org.acegisecurity.context. SecurityContextHolder. Najciekawszą z punktu widzenia tego artykułu metodą wspomnianej klasy jest statyczna metoda getContext(), która udostępnia obiekt implementujący interfejs org.acegisecurity.context.SecurityContext, czyli właśnie rzeczony kontekst. Interfejs ten definiuje metodę getAuthentication(), która zwraca z kolei obiekt implementujący interfejs org.acegisecurity.Authentication. I o to właśnie chodzi, jest to bodaj najważniejszy obiekt w całej tej zabawie. Obiekt ten tworzony jest w pewnych nieinteresujących nas w tej chwili okolicznościach i początkowo zawiera dane uwierzytelniające, np. login i hasło. Następnie, ów obiekt przekazywany jest do metody authenticate(Authentication authentication) klasy implementującej interfejs org.acegisecurity.AuthenticationManager. Jak zwykle, to jaka to jest klasa zależy od konfiguracji i jest to punkt w którym możemy podłączyć nasz mechanizm uwierzytelniania. AuthenticationManager weryfikuje dane uwierzytelniające, np. login i hasło i jeśli są one poprawne wypełnia obiekt Authentication danymi określającymi uprawnienia uwierzytelnionej tożsamości, w przeciwnym wypadku rzuca wyjątek. Uprawnienia te to tablica obiektów implementujących interfejs org.acegisecurity.GrantedAuthority i w typowej sytuacji odpowiada to tablicy nazw ról. Zauważmy, że oznacza to pobranie zestawu uprawnień danej tożsamości w momencie uwierzytelnienia. A co, gdy zestaw uprawnień zmienia się w czasie, ale nie w prostej zależności od czasu? Nie twierdzę, że nie da się zaimplementować takiego scenariusza dla Acegi, ale jest to ewidentnie droga wbrew jego architekturze, która jest zwyczajnie nie dość elastyczna. Przyjrzyjmy się teraz autoryzacji.

Autoryzacja przeprowadzana jest przez klasę implementującą interfejs org.acegisecurity. AccessDecisionManager, a dokładnie, przez jej metodę decide(Authentication authentication, Object secureObject, ConfigAttributeDefinition config). Naturalnie to, jaka klasa jest użyta zależy od konfiguracji i możemy w to miejsce podstawić własną implementację mechanizmu autoryzacji. Metoda ta nie zwraca jednak typu logicznego, który określa decyzję o dostępie. Kluczowy jest tutaj parametr secureObject, który de facto jest akcją, dla której wykonujemy autoryzację. Niestety typ parametru nie mówi nam nic o tym obiekcie, ale może to być np. implementacja interfejsu org.aopalliance.intercept.MethodInvocation. Interfejs ten w szczególności definiuje metodę proceed(), którą należy wywołać, jeśli autoryzacja się powiedzie. Jak dla mnie przedziwne rozwiązanie, ale zapewne były jakieś powody aby zrobić to w ten właśnie sposób. Stosując jedną z typowych konfiguracji wykorzystującą klasy dostarczane przez Acegi, wywołanie wspomnianej metody decide(…) interfejsu AccessDecisionManager możemy sprowadzić do wywołania metody vote(…) klasy org.acegisecurity.vote.RoleVoter z tymi samymi parametrami. Klasa ta po prostu sprawdza czy tablica obiektów GrantedAuthority zapisanych w obiekcie Authentication zawiera uprawnienia wymagane do wykonania danej akcji.

Ciekaw jestem, jaka jest wasza opinia o Acegi Security. Ja osobiście uważam, że nie jest to rozwiązanie dostosowane do środowisk korporacyjnych. Prosty model autoryzacji dla pojedynczej aplikacji, oparty na dobrze zdefiniowanych rolach owszem, ale nie wiele więcej.