Spring Security: esplorazione dell'autenticazione JDBC

Persistenza in alto

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

In questo breve tutorial, esploreremo le funzionalità offerte da Spring per eseguire l'autenticazione JDBC utilizzando una configurazione DataSource esistente .

Nella nostra Autenticazione con un post UserDetailsService supportato da database, abbiamo analizzato un approccio per raggiungere questo obiettivo, implementando noi stessi l'interfaccia UserDetailService .

Questa volta utilizzeremo la direttiva AuthenticationManagerBuilder # jdbcAuthentication per analizzare i pro ei contro di questo approccio più semplice.

2. Utilizzo di una connessione H2 incorporata

Prima di tutto, analizzeremo come possiamo ottenere l'autenticazione utilizzando un database H2 incorporato.

Questo è facile da ottenere perché la maggior parte dell'autoconfigurazione di Spring Boot è pronta per questo scenario.

2.1. Dipendenze e configurazione del database

Iniziamo seguendo le istruzioni del nostro precedente articolo Spring Boot With H2 Database a:

  1. Includere le corrispondenti dipendenze spring-boot-starter-data-jpa e h2
  2. Configurare la connessione al database con le proprietà dell'applicazione
  3. Abilita la console H2

2.2. Configurazione dell'autenticazione JDBC

Useremo di Primavera di sicurezza AuthenticationManagerBuilder aiutante di configurazione per configurare l'autenticazione JDBC:

@Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .withDefaultSchema() .withUser(User.withUsername("user") .password(passwordEncoder().encode("pass")) .roles("USER")); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }

Come possiamo vedere, stiamo usando il DataSource autoconfigurato . La direttiva withDefaultSchema aggiunge uno script di database che popolerà lo schema predefinito, consentendo la memorizzazione di utenti e autorità.

Questo schema utente di base è documentato nell'Appendice Spring Security.

Infine, stiamo creando una voce nel database con un utente predefinito a livello di codice.

2.3. Verifica della configurazione

Creiamo un endpoint molto semplice per recuperare le informazioni sul Principal autenticato :

@RestController @RequestMapping("/principal") public class UserController { @GetMapping public Principal retrievePrincipal(Principal principal) { return principal; } }

Inoltre, proteggeremo questo endpoint, consentendo l'accesso alla console H2:

@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests() .antMatchers("/h2-console/**") .permitAll() .anyRequest() .authenticated() .and() .formLogin(); httpSecurity.csrf() .ignoringAntMatchers("/h2-console/**"); httpSecurity.headers() .frameOptions() .sameOrigin(); } }

Nota: qui stiamo riproducendo la precedente configurazione di sicurezza implementata da Spring Boot, ma in uno scenario reale, probabilmente non abiliteremo affatto la console H2.

Ora eseguiremo l'applicazione e navigheremo nella console H2. Possiamo verificare che Spring sta creando due tabelle nel nostro database incorporato: utenti e autorità.

La loro struttura corrisponde a quella definita nell'appendice Spring Security di cui abbiamo parlato prima.

Infine, autentichiamo e richiediamo all'endpoint / principal di visualizzare le informazioni correlate, inclusi i dettagli dell'utente.

2.4. Sotto il cappuccio

All'inizio di questo post, abbiamo presentato un collegamento a un tutorial che spiega come possiamo personalizzare l'autenticazione basata su database implementando l' interfaccia UserDetailsService ; consigliamo vivamente di dare un'occhiata a quel post se vogliamo capire come funzionano le cose sotto il cofano.

In questo caso, ci affidiamo a un'implementazione della stessa interfaccia fornita da Spring Security; il JdbcDaoImpl .

Se esploriamo questa classe, vedremo l' implementazione UserDetails che utilizza ei meccanismi per recuperare le informazioni sull'utente dal database.

Funziona abbastanza bene per questo semplice scenario, ma presenta alcuni inconvenienti se si desidera personalizzare lo schema del database o anche se si desidera utilizzare un fornitore di database diverso.

Vediamo cosa succede se cambiamo la configurazione per utilizzare un diverso servizio JDBC.

3. Adattare lo schema per un database diverso

In questa sezione, configureremo l'autenticazione sul nostro progetto utilizzando un database MySQL.

Come vedremo in seguito, per ottenere ciò, dovremo evitare di utilizzare lo schema predefinito e fornire il nostro.

3.1. Dipendenze e configurazione del database

Per i principianti, rimuoviamo la dipendenza h2 e sostituiamola con la libreria MySQL corrispondente:

 mysql mysql-connector-java 8.0.17 

Come sempre, possiamo cercare l'ultima versione della libreria in Maven Central.

Ora reimpostiamo le proprietà dell'applicazione di conseguenza:

spring.datasource.url= jdbc:mysql://localhost:3306/jdbc_authentication spring.datasource.username=root spring.datasource.password=pass

3.2. Esecuzione della configurazione predefinita

Ovviamente, questi dovrebbero essere personalizzati per connettersi al tuo server MySQL in esecuzione. A scopo di test, qui avvieremo una nuova istanza utilizzando Docker:

docker run -p 3306:3306 --name bael-mysql -e MYSQL_ROOT_PASSWORD=pass -e MYSQL_DATABASE=jdbc_authentication mysql:latest

Eseguiamo ora il progetto per vedere se la configurazione predefinita è adatta per un database MySQL.

In realtà, l'applicazione non sarà in grado di avviarsi, a causa di un'eccezione SQLSyntaxErrorException . Questo ha davvero senso; come abbiamo detto, la maggior parte dell'autoconfigurazione predefinita è adatta per un HSQLDB.

In this case, the DDL script provided with the withDefaultSchema directive uses a dialect not suitable for MySQL.

Therefore, we need to avoid using this schema and provide our own.

3.3. Adapting the Authentication Configuration

As we don't want to use the default schema, we'll have to remove the proper statement from the AuthenticationManagerBuilder configuration.

Also, since we'll be providing our own SQL scripts, we can avoid trying to create the user programmatically:

@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource); }

Now let's have a look at the database initialization scripts.

First, our schema.sql:

CREATE TABLE users ( username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, enabled TINYINT NOT NULL DEFAULT 1, PRIMARY KEY (username) ); CREATE TABLE authorities ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, FOREIGN KEY (username) REFERENCES users(username) ); CREATE UNIQUE INDEX ix_auth_username on authorities (username,authority);

And then, our data.sql:

-- User user/pass INSERT INTO users (username, password, enabled) values ('user', '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a', 1); INSERT INTO authorities (username, authority) values ('user', 'ROLE_USER');

Finally, we should modify some other application properties:

  • Since we're not expecting Hibernate to create the schema now, we should disable the ddl-auto property
  • By default, Spring Boot initializes the data source only for embedded databases, which is not the case here:
spring.datasource.initialization-mode=always spring.jpa.hibernate.ddl-auto=none

As a result, we should now be able to start our application correctly, authenticating and retrieving the Principal data from the endpoint.

4. Adapting the Queries for a Different Schema

Let's go a step further. Imagine the default schema is just not suitable for our needs.

4.1. Changing the Default Schema

Imagine, for example, that we already have a database with a structure that slightly differs from the default one:

CREATE TABLE bael_users ( name VARCHAR(50) NOT NULL, email VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, enabled TINYINT NOT NULL DEFAULT 1, PRIMARY KEY (email) ); CREATE TABLE authorities ( email VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, FOREIGN KEY (email) REFERENCES bael_users(email) ); CREATE UNIQUE INDEX ix_auth_email on authorities (email,authority);

Finally, our data.sql script will be adapted to this change too:

-- User [email protected]/pass INSERT INTO bael_users (name, email, password, enabled) values ('user', '[email protected]', '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a', 1); INSERT INTO authorities (email, authority) values ('[email protected]', 'ROLE_USER');

4.2. Running the Application with the New Schema

Let's launch our application. It initializes correctly, which makes sense since our schema is correct.

Now, if we try to log in, we'll find an error is prompted when presenting the credentials.

Spring Security is still looking for a username field in the database. Lucky for us, the JDBC Authentication configuration offers the possibility of customizing the queries used to retrieve user details in the authentication process.

4.3. Customizing the Search Queries

Adapting the queries is quite easy. We simply have to provide our own SQL statements when configuring the AuthenticationManagerBuilder:

@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .usersByUsernameQuery("select email,password,enabled " + "from bael_users " + "where email = ?") .authoritiesByUsernameQuery("select email,authority " + "from authorities " + "where email = ?"); }

We can launch the application once more, and access the /principal endpoint using the new credentials.

5. Conclusion

Come possiamo vedere, questo approccio è molto più semplice del dover creare il nostro UserDetailServiceattuazione, che implica un processo arduo; creazione di entità e classi che implementano l' interfaccia UserDetail e aggiungono repository al nostro progetto.

Lo svantaggio è, ovviamente, la poca flessibilità che offre quando il nostro database o la nostra logica differisce dalla strategia predefinita fornita dalla soluzione Spring Security.

Infine, possiamo dare un'occhiata agli esempi completi nel nostro repository GitHub. Abbiamo anche incluso un esempio usando PostgreSQL che non abbiamo mostrato in questo tutorial, solo per mantenere le cose semplici.

Fondo di persistenza

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO