Często przygotowując aplikację webową, chcemy posiadać sekcję, która będzie dostępna tylko dla zalogowanych użytkowników lub jakiś panel administracyjny, który pozwoli na zarządzanie stroną. Spring udostępnia gotowy mechanizm autentykacji i autoryzacji użytkowników, z którego możemy w łatwy sposób skorzystać. W poście postaram się pokazać jak w najprostszy sposób skonfigurować autentykację, czyli logowanie użytkownika do systemu. Kod, który używam, jest możliwie jak najprostszy. Pomijam tutaj używanie widoków i innych komponentów skupiając się tylko na tym, co jest niezbędne do rozpoczęcia pracy z security.
Wpis jest częścią serii wpisów na temat Spring Boot. Zapraszam do zapoznania się z pozostałymi wpisami na blogu!
- Spring Boot – szybkie tworzenie aplikacji web w Javie
- Spring Boot – Interakcja z bazą danych czyli Spring Data JPA
- Spring Boot – Widoki
- Użycie Spring Security w Spring Boot
- Spring Boot Web – Przekazywanie zmiennych do aplikacji przez URL czyli użycie @RequestParam i @PathVariable
- Spring Boot Test – testowanie aplikacji w Spring Boocie
- Wsparcie dla pola typu JSONB w PostgreSQL dla Spring Data JPA + Hibernate
- Wysyłanie plików na serwer przez Spring Boot
- Spring Boot – Spring Data JPA część II: Powiązania między tabelami
- Spring Mail + Spring Boot – łatwe wysyłanie maili z aplikacji w Javie
- Docker + Spring Boot – zamykamy aplikację w kontenerze Dockerowym
- Java Bean Validation + Spring Boot – sprawdzanie poprawności danych w Spring Boocie
- Spring Boot + Swagger UI
- Serwer Amazon EC2 i SSL od Let’s encrypt w aplikacji Spring Boot
Przygotowanie
Zaczniemy od prostego projektu w Spring Boocie kompilowanego przy pomocy Mavena. Całość uruchomiona jest w Javie 8, jednak jeśli ktoś uprze się na 7, to powinno to przy minimalnym wysiłku lub nawet bez niego zadziałać.
Stwórzmy więc nowy projekt w Mavenie i dodajmy niezbędne zależności do uruchomienia projektu. Mój pom.xml wygląda następująco:
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>pl.mloza.spring.boot</groupId> | |
<artifactId>security</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
<parent> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-parent</artifactId> | |
<version>1.2.5.RELEASE</version> | |
</parent> | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
</dependency> | |
</dependencies> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-maven-plugin</artifactId> | |
</plugin> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<version>3.0</version> | |
<configuration> | |
<source>1.8</source> | |
<target>1.8</target> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
Pom zawiera definicję projektu z parentem ustawionym na spring-boot-starter-paren oraz zależność do spring-boot-starter-web która umożliwi tworzenie aplikacji web.
Następnym krokiem jest utworzenie klasy startowej, która umożliwi uruchomienie aplikacji. Moja klasa wygląda następująco:
package pl.mloza.spring.boot.security; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.ResponseBody; | |
@SpringBootApplication | |
@Controller | |
public class Main { | |
@RequestMapping("/") | |
@ResponseBody | |
public String mainAction() { | |
return "Hello World"; | |
} | |
public static void main(String[] args) { | |
SpringApplication.run(Main.class); | |
} | |
} | |
 |
Klasa zawiera funkcję main, która uruchamia aplikację Spring Boota. Jak pisałem wcześniej, ograniczam ilość tworzonego kodu do minimum, klasę startującą połączyłem od razu z kontrolerem, który zwraca Hello World jako treść strony. Aktualnie powinniśmy być w stanie uruchomić projekt i po przejściu na stronę http://localhost:8080 powinien się nam ukazać napis Hello World.
Spring Boot Security
Jeśli mamy aplikację teraz przejdźmy do jej zabezpieczenia. Do naszego poma, dodajemy zależność spring-boot-starter-security:
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-security</artifactId> | |
</dependency> |
Po odświeżeniu projektu Mavena, restarcie aplikacji i odświeżeniu strony, powinniśmy dostać zapytanie o użytkownika i hasło. Domyślna nazwa użytkownika to „user”, a hasło jest generowane losowo i wypisywane na konsolę:
2015-08-26 14:03:53.991 INFO 2652 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2015-08-26 14:03:53.991 INFO 2652 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 4092 ms 2015-08-26 14:03:55.781 INFO 2652 --- [ost-startStop-1] b.a.s.AuthenticationManagerConfiguration : Using default security password: 3931d77b-230d-4ef6-9be0-95bca8ab77ab 2015-08-26 14:03:55.891 INFO 2652 --- [ost-startStop-1] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: Ant [pattern='/css/**'], [] 2015-08-26 14:03:55.891 INFO 2652 --- [ost-startStop-1] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: Ant [pattern='/js/**'], []
Wpisanie poprawnych danych pozwoli nam zobaczyć napis Hello World, w przypadku podania nieprawidłowych danych system poprosi o nie jeszcze raz i kolejny. Anulowanie spowoduje wyświetlenie domyślnej strony błędu.
Hasło można również ustawić w konfiguracji aplikacji za pomocą właściwości security.user.password.
Konfiguracja użytkownika i hasła
Jedną z możliwości jest podanie nazwy użytkownika i hasła bezpośrednio w kodzie. Nie jest to dobrą praktyką, ale dla bardzo prostych potrzeb jest to wystarczające. Dodatkowo posłuży to jako punkt wyjścia do kolejnych ulepszeń.
Zacznijmy od stworzenia klasy konfiguracyjnej dla security, gdzie dodamy kod tworzący użytkowników. Może to wyglądać następująco:
package pl.mloza.spring.boot.security.config; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; | |
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | |
@Configuration | |
public class SecurityConfig extends WebSecurityConfigurerAdapter { | |
@Autowired | |
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { | |
auth.inMemoryAuthentication() | |
.withUser("michal") | |
.password("password") | |
.roles("USER", "ADMIN"); | |
} | |
} |
Klasa powinna rozszerzać klasę WebSecurityConfigurerAdapter oraz posiadać adnotację @Configuration. W ten sposób zostanie automatycznie załadowana i uruchomiona. W metodzie configureGlobal otrzymujemy obiekt klasy AuthenticationManagerBuilder, dzięki której możemy dodać użytkowników. Jeśli potrzebujemy więcej niż jednego użytkownika, możemy zamienić kod metody na coś takiego:
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer = | |
auth.inMemoryAuthentication(); | |
authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer | |
.withUser("michal") | |
.password("password") | |
.roles("USER", "ADMIN"); | |
authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer | |
.withUser("user2") | |
.password("passwd") | |
.roles("USER"); |
W ten sposób możemy dodawać kolejnych użytkowników. Następnie restartujemy aplikację i próbujemy się logować za pomocą podanych danych.
Zabezpieczanie tylko wybranych stron
W domyślnej konfiguracji zabezpieczamy wszystkie strony, jednak w zdecydowanej większości część stron powinna być dostępna dla użytkowników. Spróbujmy zmienić konfigurację tak, aby strona główna była dostępna dla wszystkich a zasób pod adresem /loggeduser wymagał zalogowania. Zacznijmy od dodania do kontrolera metody obsługującej zasób /loggeduser, do klasy Main dodajemy metodę z adnotacją:
@RequestMapping("/loggeduser") | |
@ResponseBody | |
public String loggedUserAction() { | |
return "Hello User"; | |
} |
Metoda ta nie robi nic innego, jak tylko zwraca napis Hello User. Następnie przejdźmy do konfiguracji security w klasie SecurityConfig i dodajmy metodę config z zawartością:
@Override | |
protected void configure(HttpSecurity http) throws Exception { | |
http | |
.authorizeRequests() | |
.antMatchers("/").permitAll() | |
.antMatchers("/loggeduser").authenticated() | |
.and().formLogin(); | |
} |
Metoda ta definiuje sposób logowania oraz jakie zasoby mają być chronione a jakie dostępne. Metoda antMatchers może przyjmować adresy z gwiazdkami więc można w łatwy sposób zdefiniować, co ma być chronione. Po zdefiniowaniu adresów zasobów mówimy, co ma być zrobione, permitAll zezwala na dostęp, authenticated wymaga autentykacji. Na końcu mamy formLogin, mówi to, że dozwolone jest logowanie za pomocą formularza. Sam formularz jest generowany automatycznie, jak go zastąpić pokażę w innym poście. Jeśli zabraknie ostatniego wywołania w odpowiedzi na żądanie chronionego zasobu, otrzymamy błąd 403 bez możliwości zalogowania. Dodając ostatnie wywołanie, zostaniemy przekierowani na stronę z formularzem. Po restarcie aplikacji i wejściu na stronę główną powinniśmy zobaczyć napis Hello World bez potrzeby logowania. Wejście na adres http://localhost:8080/loggeduser przekieruje nas na stronę logowania, po podaniu danych logowania zostaniemy przeniesieni na docelową stronę i zobaczmy napis „Hello User”.
Podsumowanie
Post ten pokazuje podstawową konfigurację Spring Security. Dzięki temu jesteśmy w stanie zapewnić podstawową ochronę stron, udostępnić je tylko wybranym osobom. Biblioteka dostarcza możliwość autentykacji za pomocą między innymi JDBC, LDAPA lub pozwala na napisanie własnego sterownika autentykacji i autoryzacji. Jak tego dokonać postaram się pokazać w kolejnym poście.
Spoko!
Jeszcze bardziej gdyby był przykład z wyciąganiem userów z bazy danych mysql.
Oraz zastąpienie tego generowanego formularza własnym.
Ale ze ten wpis 10/10 🙂
Dziękuje! Dodanie interakcji z bazą i formularza mam w planie, niestety ostatnio natłok innych obowiązków blokuje pisanie postów.
A szkoda, że brak czasu, bo naprawdę prowadzisz bloga w świetny sposób. Już wcześniej skomentowałem, zrobiłem sobie przerwę na inne rzeczy od kodzenia, teraz wróciłem i znowu muszę Cię pochwalić – jesteś świetny w tworzeniu poradników w formie tekstowej. Naprawdę, jeśli stworzyłbyś bardziej kompletny, rozbudowany zbiór porad – to myślę, że to mogłaby być najlepsza polskojęzyczna strona dla osób, które chcą się nauczyć programować, ale nie są otrzaskane w środowisku informatycznym, nie poruszają się w nim zbyt sprawnie. Trzymam kciuki, zmieniasz Polskę!