Proste uwierzytelnianie i autoryzacja w aplikacji webowej ze Spring Security w 15 minut

Z Jacek Laskowski - Wiki Projektanta Java EE

Wdrożenie mechanizmu uwierzytelnienia i autoryzacji w aplikacji webowej wielu napawa lękiem, co zwykle kończy się omijaniem tematu szerokim łukiem. Niektórzy jednak są aż nadto gorliwi w swoich postanowieniach rozwojowych, że implementują go od podstaw. W oby przypadkach otrzymamy aplikację potencjalnie źle zabezpieczoną. Skorzystanie z gotowego rozwiązania jakim jest Spring Security, to nie tylko skrócenie czasu wdrożenia funkcjonalności, ale przede wszystkim zmniejszenie kosztów późniejszego jej utrzymania (szanse na znalezienie pracownika z odpowiednim przygotowaniem - znajomością Spring Security - są większe niż własnego). W tym artykule zademonstruję, w jaki sposób otwarty projekt Spring Security może być wartościowym rozwiązaniem w kwestii wdrożenia mechanizmu uwierzytelniania i autoryzacji w aplikacji webowej. Jakkolwiek artykuł przedstawia wyłącznie uwierzytelnianie na bazie automatycznie generowanych ekranów logowania z bazą użytkowników w pliku konfiguracyjnym Spring Security, to jest on dobrym punktem startowym do wdrożenia bardziej zaawansowanej konfiguracji opartej na LDAP i CAS. Spring Security udostępnia całą gamę możliwości w tym zakresie.

Spis treści

Zestawienie środowiska programistycznego

Rozpoczynamy od zestawienia środowiska programistycznego. Użyjemy rozwojowej wersji NetBeans IDE 6.7 (jednakże nie wykorzystano żadnych specyficznych dla tej wersji funkcjonalności - pewnie poza poprawkami znalezionych błędów w wersji 6.5 i kilkoma nowymi) oraz Spring Framework 2.5.6 i Spring Security 2.0.4. Zarządzanie zależnościami projektowymi możnaby rozwiązać posiłkując się Apache Maven, Apache Ivy, Apache Buildr czy podobnymi projektami pobierającymi je automatycznie, ale uproszczenie artykułu wzięło górę i będziemy posiłkować się wyłącznie funkcjonalnością dostępną w NetBeans. Serwerem aplikacyjnym jest GlassFish v2.1 (wyłącznie ze względu na jego nietrywialne wsparcie przez samego NetBeans). Korzystający z wersji produkcyjnej NetBeans 6.5 mogą pobrać wersję z serwerem GlassFish już zintegrowanym ze środowiskiem jako zestaw Java lub All - NetBeans IDE Download Bundles.

Utworzenie projektu aplikacji webowej - SpringSecuritySecuredWebApp

Nasze prace rozpoczniemy od utworzenia projektu aplikacji webowej. Otwieramy asystenta Web Application, który przeprowadzi nas przez cały proces założenia właściwego projektu webowego w NetBeans. Wybieramy menu File > New Project (lub po prostu Ctrl+Shift+N)

Grafika:netbeans-newproject.png

a następnie w kategorii Java Web wybieramy Web Application.

Grafika:netbeans-javaweb-webapplication.PNG

Wciskamy Next >.

W tym momencie możemy być poproszeni o włączenie obsługi projektów Java EE (nowa cecha NetBeans 6.7).

Grafika:netbeans-javaee-activate.png

Włączamy przyciskając Activate, który uaktywni przycisk Next >. Kontynuujemy wciskając go.

Uzupełniamy dane aplikacji webowej. W polu Project Name wpisujemy SpringSecuritySecuredWebApp.

Grafika:netbeans-nameandlocation.png

Wciskamy Next >.

Definiowanie serwera uruchomieniowego - GlassFish V2

W zależności od naszego wcześniejszego przygotowania, możemy spotkać się z panelem, w którym pojawi się komunikat braku definicji serwera w NetBeans.

Grafika:netbeans-noserversregistered.png

Definiujemy jeden korzystając z przycisku Add... przy polu Server. Wciskamy przycisk Add... i z listy wspieranych (domyślnie) serwerów wybieramy GlassFish V2 (powinien być zaznaczony domyślnie).

Grafika:netbeans-chooseserver.png

Wciskamy Next >.

Wskazujemy na katalog domowy serwera GlassFish w polu Server Location. Poprawnie wpisany katalog spowoduje, że NetBeans zaproponuje jedną z dostępnym domen (struktury katalogowej z definicjami serwera).

Grafika:netbeans-serverlocation.png

Wciskamy Next >.

Podajemy hasło administracyjne GlassFisha - domyślnie admin/adminadmin.

Grafika:netbeans-addserverinstance.png

Wciskamy Finish, po którym pojawi się ekran z wybranym serwerem.

Grafika:netbeans-serverandsettings.png

Wciskamy Finish (panel, który pojawiłby się po Next > dotyczy możliwych do włączenia w aplikacji szkieletów aplikacyjnych, który w tej chwili nas nie interesuje). Tym samym zdefiniowaliśmy projekt aplikacji webowej w NetBeans.

Grafika:netbeans-webapp.png

Sprawdzenie aplikacji webowej bez bezpieczeństwa

Sprawdzamy poprawność działania aplikacji webowej bez wdrożonego mechanizmu bezpieczeństwa.

Z menu kontekstowego projektu wybieramy menu Run. Po chwili aplikacja powinna być dostępna pod http://localhost:8080/SpringSecuritySecuredWebApp/.

Grafika:netbeans-helloworld.png

Komunikat Hello World! jest gwarancją poprawnego działania aplikacji.

(opcjonalne) Zatrzymanie serwera GlassFish V2

Podczas zmian w projekcie aplikacji NetBeans będzie automatycznie wdrażał aplikację na GlassFisha. Warto więc wyłączyć serwer, jeśli wprowadzamy wiele zmian, z których niektóre mogą powodowąć błędy w aplikacji, np. częściowe (przejściowe) zmiany w konfiguracji i w ten sposób niepotrzebnie obciążają NetBeansa.

Wyłączamy serwer GlassFish przez zakładkę Services, a tam Servers i z menu kontekstowego serwera (pod prawym przyciskiem myszki) GlassFish V2 wybieramy Stop.

Grafika:netbeans-glassfish-stop.png

Wdrożenie bezpieczeństwa ze Spring Security

Stworzenie bibliotek projektowych - SpringFramework256 i SpringSecurity204

Definiujemy wykorzystywane przez nas projekty jako biblioteki projektowe NetBeans - Spring Framework 2.5.6 i Spring Security 2.0.4. Pierwszy z nich - Spring Framework - stanowi podstawę technologiczną dla drugiego - Spring Security. Pobieramy ich wersje dystrybucyjne ze strony Spring Downloads.

Z menu Tools wybieramy Libraries.

Grafika:netbeans-tools-libraries.png

I definiujemy biblioteki SpringFramework256 oraz SpringSecurity204 (z nieznanych powodów nazwa biblioteki nie może zawierać spacji). Wybieramy przycisk New Library..., a następnie w polu Library Name wpisujemy nazwę biblioteki.

Grafika:netbeans-newlibrary.PNG

Wciskamy przycisk OK.

Dodajemy potrzebne pliki wciskając przycisk Add JAR/Folder i wskazując na pliki jar w katalogu domowym Spring Framework (możliwe jest wybranie wielu jednocześnie przytrzymując Ctrl bądź Shift, tj. typowe zasady edycji obowiązują) - antlr-2.7.6.jar, commons-*.jar (bez commons-attributes-compiler.jar) oraz spring.jar.

Grafika:netbeans-springframework256.png

To samo robimy dla SpringSecurity204 - spring-security-acl-2.0.4.jar, spring-security-cas-client-2.0.4.jar, spring-security-core-*-2.0.4.jar oraz spring-security-taglibs-2.0.4.jar.

Grafika:netbeans-springsecurity204.PNG

Zamykamy okno bibliotek przyciskiem OK.

Dodawanie bibliotek projektowych

Nowe biblioteki SpringFramework256 oraz SpringSecurity204 dodajemy do projektu. Z menu kontekstowego projektu (pod prawym przyciskiem myszki na projekcie) wybieramy Properties.

Grafika:netbeans-properties.png

Wybieramy kategorię Libraries i dodajemy zdefiniowane wcześniej biblioteki - przycisk Add Library.... Zaznaczamy pole wyboru Package dla obu (powinno być zaznaczone domyślnie).

Grafika:netbeans-projectproperties-libraries.png

Zatwierdzamy przyciskiem OK.

Aktywowanie Spring Framework - WEB-INF/web.xml

Ze sprawdzonym projektem możemy rozpocząć zmiany w zakresie bezpieczeństwa. Aktywujemy Spring Framework przez klasę nasłuchującą org.springframework.web.context.ContextLoaderListener w deskryptorze wdrożenia WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
  xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext*.xml</param-value>
  </context-param>
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

Konfiguracja Spring Security - applicationContext-security.xml

W katalogu WEB-INF tworzymy nowy plik applicationContext-security.xml. Z menu kontekstowego wybieramy New > Other...

Grafika:netbeans-new-other.png

wybieramy kategorię XML, a następnie typ dokumentu - XML Document.

Grafika:netbeans-xml-xmldocument.png

Wciskamy przycisk Next >.

Wpisujemy nazwę pliku applicationContext-security (bez rozszerzenia xml, który jest automatycznie dodawany przez NetBeans) w polu File Name.

Grafika:netbeans-newxmldocument.png

Wciskamy przycisk Next >, a następnie Finish.

Podmieniamy zawartość pliku na poniższą, w której deklaratywnie definiujemy pojedynczego użytkownika jacek z hasłem passw0rd, któremu przypisujemy rolę ROLE_USER (znacznik user w authentication-provider/user-service). Przez intercept-url w http określamy, że wyłącznie rola ROLE_USER ma prawo wejść do naszej aplikacji (pattern="/**" oznacza wszystkie zasoby w aplikacji).

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
  <http auto-config='true'>
    <intercept-url pattern="/**" access="ROLE_USER"/>
  </http>
  <authentication-provider>
    <user-service>
      <user name="jacek" password="passw0rd" authorities="ROLE_USER"/>
    </user-service>
  </authentication-provider>
</beans:beans>

W pliku applicationContext-security.xml opisujemy (deklaratywnie) konfigurację bezpieczeństwa, zamiast ją oprogramowywać. Jest to bez wątpienia zdecydowany zysk na czasie.

Zauważmy, że wskazanie na pliki XSD konfiguracji springowej nie posiada numeru wersji, co nie jest powszechnie promowane, czy to w samej dokumentacji Springa, czy wielu artykułów na Sieci. Znacznie upraszcza możliwą migrację do innych (zapewne nowszych) wersji.

Modyfikacja index.jsp

Jako ostatni krok w uzbrojeniu naszej aplikacji webowej w mechanizm uwierzytelnienia i autoryzacji zmienimy zawartość pliku index.jsp na poniższą:

<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <h1>Witaj <%= request.getRemoteUser()%>!</h1>
  </body>
</html>

Zmiana polega na wyświetleniu użytkownika przez standardowe (ze specyfikacji Java Servlet API) wywołanie request.getRemoteUser(). Po poprawnym uwierzytelnieniu Spring Security pozwoli na dostęp użytkownika do aplikacji i przypisze użytkownika do żądania. Warto zauważyć, że nie wykorzystujemy nic specyficznego dla Spring Security.

Uruchomienie

Potwierdzeniem poprawnego wdrożenia zmian jest ponowne uruchomienie aplikacji (menu Run) i sprawdzenie, czy dostęp do niej będzie wymagał uwierzytelnienia użytkownika.

Grafika:netbeans-springsecurity-loginpage.PNG

Faktycznie tak jest. Teraz pozostaje już tylko sprawdzić, czy wprowadzenie pary jacek/passw0rd umożliwi dostęp do aplikacji.

Grafika:netbeans-springsecurity-witajjacek.PNG

I ponownie test wypadł pomyślnie. Jeszcze jeden z niepoprawnymi danymi.

Grafika:netbeans-springsecurity-loginnotsuccessful.PNG

Teraz pozostaje rozbudować konfigurację Spring Security o źródła danych o użytkownikach, aby można było zarządzać nimi właściwymi narzędziami niż przez edycję pliku konfiguracyjnego aplikacji, którą z kolei należałoby po każdej zmianie ponownie wdrażać na serwer.

Osobiste