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.

3 komentarze:

Unknown pisze...

Nie zgodzę się, że Acegi oferuje niewiele więcej. Zapoznałeś się może z ACL (org.acegisecurity.acl)?
Zgadzam się, że w tym aspekcie dokumentacja Acegi jest dosyć uboga i mogłoby to być łatwiejsze, ale udało mi się przy pomocy Acegi zrealizować dosyć elastyczny mechanizm autoryzacji: możliwość dynamicznego przydzielania uprawnień poszczególnym użytkownikom i grupom użytkowników.
Przykład: zezwolenie na czytanie, tworzenie wiadomości w wybranych kategoriach i podkategoriach, dziedziczenie uprawnień podkategorii z kategorii nadrzędnych oraz możliwość ich "przykrywania" (np. można dać grupie użytkowników prawo do czytania wiadomości w danej kategorii, z wyłączeniem jednego użytkownika, który też należy do tej grupy).

Dosyć proste jest też filtrowanie kolekcji, by wyrzucić np. te do których użytkownik nie ma dostępu (AfterInvocationCollectionFilteringProvider).

Swoją drogą znasz może jakieś alternatywy dla Acegi?
Kiedy implementowałem mechanizm zabezpieczeń nie udało mi się znaleźć nic co oferowałoby takie możliwości jakie daje Acegi.

Mariusz Lipiński pisze...

Przyznam, że słyszałem i czytałem troche o ACL w Acegi ale tak jak napisałeś, dokumentacja tych mechaniznów jest tak marna, że szybko się zniechęciłem. Tym bardziej, że nie było to mi akurat potrzebne. Niestety nie znam ciekawych alternatyw dla Acegi, sam używam właśnie Acegi i wcale nie uważam, że się to do niczego nie nadaje. Przeciwnie, jest całkiem użyteczne, tyle że raczej zaplanowane z myślą o - tak jak napisałem - zabezpieczaniu pojedyńczych aplikacji i to bazując na prostym modelu uprawnień opartym li tylko o role.

Tak przy okazji - super by było jak byś wrzucił jakiś prosty przykład użycia ACL'i. A może znasz jakieś ciekawe źródła wiedzy na ten temat? Może gdzieś ktoś o tym blogował?

I jeszcze dodam, że nosze się z zamiarem implementacji własnych bibliotek do zabezpieczania aplikacji, naturalnie w miarę możliwości w oparciu o istniejący kod, np. Acegi. Ale to jeszcze za wcześnie, żeby czynić jakiekolwiek deklaracje. Jeden z mechanizmów który można by w tym celu wykożystać opisałem w artykule "Dynamiczne proxy i adnotacje, czyli dobrana para" i... w komentarzu do tegoż artykułu.

Unknown pisze...

Starałem się to jakoś szybko i zwięźle opisać, ale i tak wyszło sporo kodu:
http://jdn.pl/node/1349

Mimo wszystko zapraszam do lektury, mam nadzieję, że okaże się przydatne.