Proseguiamo la serie su Spring Security evolvendo la semplice applicazione web presentata nel precedente articolo Primi Passi con Spring Security (parte 1) in cui abbiamo visto come configurare il progetto al fine di implementare il processo di autenticazione.
Authentication Provider
Spring Security fornisce diverse opzioni per l’esecuzione del processo di autenticazione. Il più semplice è quello visto nell’esempio in cui gli utenti sono elencati nel file di contesto che definisce il security context, ma ovviamente non è l’approccio più sicuro e comunque non è consigliato per il rilascio produzione. Più probabilmente le informazioni utente vengono recuperate dal database applicativo o attraverso servizi di autenticazione tipo LDAP.
Custom Provider
Il primo caso che vediamo è quello di una implementazione del processo di autenticazione attraverso un bean che implementa l’interfaccia UserDetailsService
di Spring implementando il metodo loadUserByUsername()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class UserService implements UserDetailsService { private BCryptPasswordEncoder bcryptEncoder = new BCryptPasswordEncoder(4); public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if ( username.equalsIgnoreCase( "user1") ) { List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add( new SimpleGrantedAuthority("ROLE_USER") ); UserDetails user = new User("user1", bcryptEncoder.encode("password1"), authorities); return user; } throw new UsernameNotFoundException("User not found"); } } |
Input al metodo è la username inserita nella form di login mentre come output è restituito un oggetto che implementa l’interfaccia UserDatails
, contenente le informazioni sull’utente “recuperato”. Nell’esempio è ritornato un oggetto User
istanziato con la username, la password ed i ruoli dell’utente, ma è anche possibile specificare se l’utente è abilitato, oppure se l’account o la password sono scaduti. Si noti infine che, secondo specifica, il metodo non deve restituire NULL
il caso in cui l’utente non esista, ma sollevare l’eccezione UsernameNotFoundException
.
Diversamente dal caso semplice visto nell’esempio originale, in questo caso è stata utilizzato un encoder per la password. Al solito è possibile implementare il proprio encoder estendendo l’interfaccia PasswordEncoder
di Spring, e lo stesso framework ne fornisce diversi, tra cui ovviamente il NoOpPasswordEncoder
che non esegue nessuna operazione. Nella classe UserService
però abbiamo preferito utilizzare il BCryptPasswordEncoder fornito da Spring e che implementa l’algoritmo BCrypt.
La registrazione del provide UserService
da noi implementato avviene sul file spring-security.xml
utilizzando il tag <authentication-provider>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<b:beans ...> <http /> <b:bean name="userService" class="it.javaboss.UserService"/> <b:bean name="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/> <authentication-manager> <authentication-provider user-service-ref="userService"> <password-encoder ref="bcryptEncoder"/> </authentication-provider> <authentication-provider> <user-service> <user name="user" password="{noop}password" authorities="ROLE_USER" /> </user-service> </authentication-provider> </authentication-manager> </b:beans> |
Si noti che è possibile utilizzare più tag <authentication-provider>
ciascuno con metodi di autenticazione (ed encoding) differenti.
Database Provider
Prima di procedere con gli esempi introduciamo nel progetto il database H2, inserendo la dipendenze necessarie nel file pom.xml
:
1 2 3 4 5 6 7 8 9 10 11 |
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.196</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.3.RELEASE</version> </dependency> |
Inoltre creiamo un file db-h2-config.xml
nella cartella /spring
in cui configuriamo il datasource, gli script per la creazione della base dati, secondo la struttura prevista dal framework, e gli script per l’inserimento degli utenti.
1 2 3 4 5 6 |
<beans ... > <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:create-db.sql" /> <jdbc:script location="classpath:insert-data.sql" /> </jdbc:embedded-database> </beans> |
Per l’accesso alla base dati così definita per il recupero delle informazioni utente è possibile seguire due strade distinte. La prima è quella di definire esplicitamente un bean di tipo JdbcDaoImpl
, classe Spring che estende UserDetailsService
, e che utilizza il datasource specificato in configurazione per implementare il servizio loadUserByUsername()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<b:beans ...> <http /> <b:bean name="nopeEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/> <b:bean id="jdbcUserService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <b:property name="dataSource" ref="dataSource"/> </b:bean> <authentication-manager> <authentication-provider user-service-ref="jdbcUserService"> <password-encoder ref="nopeEncoder"/> </authentication-provider> </authentication-manager> </b:beans> |
La seconda strada è quella di utilizzare il tag <jdbc-user-service>
che istanzia direttamente un bean che implementa UserDetailsService
ed utilizzando un datasource per l’accesso alla base dati degli utenti.
1 2 3 4 5 6 7 8 9 10 |
<b:beans ...> <http /> <authentication-manager> <authentication-provider> <password-encoder ref="nopeEncoder"/> <jdbc-user-service data-source-ref="dataSource"/> </authentication-provider> </authentication-manager> </b:beans> |
Custom database
In generale è abbastanza improbabile che la nostra base dati sia conforme allo schema previsto dal Database Provider di Spring Security. Per tale motivo il tag <jdbc-user-service>
prevede i seguenti attributi:
users-by-username-query |
Consente di definire la query SLQ per il recupero delle informazioni utente. La query di default è:select username, password, enabled |
authorities-by-username-query |
Consente di definire la query SLQ per il recupero dei ruoli dell’utente. La query di default è:select username, authority |
Codice Sorgente
Il codice sorgente completo degli esempi presentati è scaricabile qui spring-security.