Spring Security - jak skonfigurować?

W poprzednim wpisie opisałem jak działa Spring Security. Przedstawię Wam teraz jak wygląda implementacja tych rzeczy. W przykładach używam Java Configuration. Aplikacja, która zostaje zabezpieczona jest aplikacją internetową. Wybrałem taką, ponieważ to do takich aplikacji najczęściej jest ten framework używany.

Inicjatory

Klasy, które rozpoczynają pracę z aplikacjami internetowymi. Serwer aplikacyjny inicjuje kontekst oraz inne z tym związane rzeczy właśnie przez takiego rodzaju klasy. Jest to alternatywa dla web.xml. U mnie takim inicjatorem jest poniższa klasa:

Ona sama w sobie nic nie robi. Jednakże rozszerza ona klasę abstrakcyjną AbstractSecurityWebApplicationInitializer. Klasa bazowa robi za nas całą konfigurację, którą potrzebujemy aby rozpocząć pracę z Spring Security dla aplikacji webowych. Tworzy ona kontekst aplikacji, jeżeli użyliśmy konstruktora klasy bazowej z argumentem klas konfiguracyjnych (np. wywołując ją w konstruktorze klasy podrzędnej) oraz dodaje filtr do zapytań aplikacji, który będzie wywoływał nasze zabezpieczenie.

Mamy więc już uruchomione filtrowanie zapytań. Musimy teraz przekazać aplikacji jak ma weryfikować użytkownika. Robimy to w pliku konfiguracyjny. U mnie to oczywiście Java Configuration wylądający tak:

Zacznijmy od początku tej klasy. Rozszerza ona klasę WebSecurityConfigurerAdapter, która posiada podstawową konfigurację webowego spring security. A jest ona następująca:

  • Uwierzytelniaj i autoryzuj na każdym adresie. Używaj do tego podstawowej wersji formularza logowania.
  • Ustawia możliwość wylogowania się, weryfikację csrf tokena, ustawia filtr weryfikujący oraz inne obiekty tj. header, sessionManager, securityContext, requestCache, anonymous, servletApi.

Znamy już podstawową konfigurację, trzeba teraz zmodyfikować ją do swoich potrzeb. Tworzymy więc metodę w której zdefiniujemy jak będzie weryfikowany użytkownik. W przedstawionej wcześniej klasie konfigurujemy weryfikacje na podstawie bazy danych. Nad metodą musimy dodać adnotacje @Autowired, która w trakcie tworzenia obiektu, uruchomi tą funkcję z stworzonym wcześniej AuthenticationManagerBuilder'em. My tylko dodamy dodatkową naszą konfigurację. To samo jest z uzyskaniem dostępu do dataSource. Tutaj też używamy tej adnotacji. A więc co robi następujący kod?

auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder())
    .usersByUsernameQuery(
        "select username, password, enabled from users where username=?")
    .authoritiesByUsernameQuery(
        "select u.username, r.name from user_roles ur, users u, roles r where u.username=? and u.user_id = ur.user_id and ur.role_id = r.role_id");

Tutaj przydałby się opis kreacyjnego wzorca projektowego Builder. W skrócie mogę powiedzieć, że celem tego wzorca jest zbudowanie obiektu wykorzystując ciąg funkcji. Na samym końcu używa się funkcji zatwierdzającej (w StringBuilder jest to toString(), a w AuthenticationManagerBuilder jest to metoda build()).

Dodajemy tutaj właśnie do naszego ciągu kolejne wartości lub funkcjonalności dla AuthenticationManager'a. Na początku zaznaczamy, że chcemy skorzystać z Java DataBase Connectivity do uwierzytelniania. Po wywołaniu tej funkcji otrzymujemy następnego budowniczego dla konfiguracji JDBC. U w nim stawiamy źródło danych, koder, zapytanie weryfikujące użytkownika oraz zapytanie sprawdzające role, które użytkownik posiada.

Posiadamy, więc już konfigurację sprawdzania użytkownika. Oczywiście w bazie potrzebujemy takich tabel oraz danych w tych tabelach, aby się zalogować.

Przedstawimy teraz jakie adresy oraz jak ma weryfikować nasz filtr:

http.authorizeRequests()
    .antMatchers("/resources/**").permitAll() 
        .anyRequest().authenticated()
        .and()
    .formLogin()
        .loginPage("/login")
        .permitAll()
        .and()
    .logout()                                    
        .permitAll().and().rememberMe().
            tokenValiditySeconds(1209600).and().csrf().disable();

Przekazujemy modułowi zabezpieczeń, że resources są dostępne dla wszystkich. Następnie dla wszystkich innych adresów ma być użytkownik zalogowany. Do logowania korzystamy z strony pod adresem /login, do której mają dostęp wszyscy i analogicznie do wylogowania. Ustawiamy potem opcję zapamiętania zalogowanego użytkownika poprzez token na 2 tygodnie. Wyłączamy zabezpieczenie przed csrf, bo aktualnie nam nie jest potrzebny.

Po tym wszystkim pamiętamy o umieszczeniu adnotacji @EnableWebSecurity nad klasą konfiguracyjną.

Ciekawostka - jak zweryfikować czy jest zalogowany jakiś użytkownik?

Wspominałem w poprzednim wpisie, że anonimowy użytkownik też posiada swój token uwierzytelniający. Aby sprawdzić czy użytkownik jest zalogowany i nie jest użytkownikiem anonimowych wystarczy taki kod:

/**
* Checking user is not anonymous
* @return {@code true} if is not anonymous
*/
private boolean isNotAnonymousUser(){
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    return !(auth == null || auth instanceof AnonymousAuthenticationToken);
}

Podsumowanie

Jak widać konfiguracja prostego i działającego modułu zabezpieczającego nie należy do trudnych spraw. Wystarczy użyć kilka gotowych klas oraz nadpisać dwie metody i zaczyna nam to jakoś działać. Oczywiście moduł security pozwala na bardziej skomplikowane rzeczy. Zapraszam do zapoznania się z teorią związaną z Spring Security, która na pewno rozjaśni kilka rzeczy z nim związanych. Rozwijający się moduł security jest umieszczony na platformie github.

Comments

Popular posts from this blog

Why TDD is bad practice?

How correctly imitate dependencies?

Software development using Angular 5 with Spring Boot