Keamanan Musim Semi: Menjelajahi Otentikasi JDBC

Ketekunan teratas

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya

1. Ikhtisar

Dalam tutorial singkat ini, kita akan menjelajahi kemampuan yang ditawarkan oleh Spring untuk melakukan Autentikasi JDBC menggunakan konfigurasi Sumber Data yang ada .

Dalam posting Otentikasi dengan UserDetailsService yang didukung Database, kami menganalisis satu pendekatan untuk mencapai ini, dengan mengimplementasikan sendiri antarmuka UserDetailService .

Kali ini, kita akan menggunakan direktif AuthenticationManagerBuilder # jdbcAuthentication untuk menganalisis pro dan kontra dari pendekatan yang lebih sederhana ini.

2. Menggunakan Koneksi H2 Tertanam

Pertama-tama, kita akan menganalisis bagaimana kita dapat mencapai otentikasi menggunakan database H2 yang disematkan.

Ini mudah dicapai karena sebagian besar konfigurasi otomatis Spring Boot disiapkan di luar kotak untuk skenario ini.

2.1. Dependensi dan Konfigurasi Database

Mari kita mulai dengan mengikuti instruksi dari posting Spring Boot With H2 Database kami sebelumnya untuk:

  1. Sertakan dependensi spring-boot-starter-data-jpa dan h2 yang sesuai
  2. Konfigurasikan koneksi database dengan properti aplikasi
  3. Aktifkan konsol H2

2.2. Mengonfigurasi Otentikasi JDBC

Kami akan menggunakan pembantu konfigurasi AuthenticationManagerBuilder Spring Security untuk mengonfigurasi JDBC Authentication:

@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(); }

Seperti yang bisa kita lihat, kita menggunakan Sumber Data yang dikonfigurasi otomatis . Perintah withDefaultSchema menambahkan skrip database yang akan mengisi skema default, memungkinkan pengguna dan otoritas untuk disimpan.

Skema pengguna dasar ini didokumentasikan di Spring Security Appendix.

Terakhir, kami membuat entri dalam database dengan pengguna default secara terprogram.

2.3. Memverifikasi Konfigurasi

Mari buat titik akhir yang sangat sederhana untuk mengambil informasi Prinsipal yang diautentikasi :

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

Selain itu, kami akan mengamankan titik akhir ini, sambil mengizinkan akses ke konsol 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(); } }

Catatan: di sini kami mereproduksi konfigurasi keamanan sebelumnya yang diimplementasikan oleh Spring Boot, tetapi dalam skenario kehidupan nyata, kami mungkin tidak akan mengaktifkan konsol H2 sama sekali.

Sekarang kita akan menjalankan aplikasi dan menelusuri konsol H2. Kami dapat memverifikasi bahwa Spring membuat dua tabel di database tertanam kami: pengguna dan otoritas.

Strukturnya sesuai dengan struktur yang ditentukan dalam Lampiran Keamanan Musim Semi yang kami sebutkan sebelumnya.

Terakhir, mari mengautentikasi dan meminta titik akhir / principal untuk melihat informasi terkait, termasuk detail pengguna.

2.4. Dibawah tenda

Di awal posting ini, kami menyajikan tautan ke tutorial yang menjelaskan bagaimana kami dapat menyesuaikan otentikasi yang didukung database yang mengimplementasikan antarmuka UserDetailsService ; kami sangat menyarankan untuk melihat pos itu jika kami ingin memahami bagaimana segala sesuatunya bekerja di bawah tenda.

Dalam kasus ini, kami mengandalkan implementasi antarmuka yang sama yang disediakan oleh Spring Security; yang JdbcDaoImpl .

Jika kita menjelajahi kelas ini, kita akan melihat implementasi UserDetails yang digunakannya, dan mekanisme untuk mengambil informasi pengguna dari database.

Ini bekerja cukup baik untuk skenario sederhana ini, tetapi memiliki beberapa kekurangan jika kita ingin menyesuaikan skema database, atau bahkan jika kita ingin menggunakan vendor database yang berbeda.

Mari kita lihat apa yang terjadi jika kita mengubah konfigurasi untuk menggunakan layanan JDBC yang berbeda.

3. Mengadaptasi Skema untuk Database Berbeda

Di bagian ini, kami akan mengonfigurasi otentikasi pada proyek kami menggunakan database MySQL.

Seperti yang akan kita lihat selanjutnya, untuk mencapai ini, kita harus menghindari penggunaan skema default dan menyediakan skema kita sendiri.

3.1. Dependensi dan Konfigurasi Database

Sebagai permulaan, mari kita hapus ketergantungan h2 dan menggantinya dengan perpustakaan MySQL yang sesuai:

 mysql mysql-connector-java 8.0.17 

Seperti biasa, kami dapat mencari versi terbaru perpustakaan di Maven Central.

Sekarang mari kita setel ulang properti aplikasi sesuai:

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

3.2. Menjalankan Konfigurasi Default

Tentu saja, ini harus disesuaikan untuk terhubung ke server MySQL Anda yang sedang berjalan. Untuk tujuan pengujian, di sini kita akan memulai instance baru menggunakan Docker:

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

Mari kita jalankan proyek sekarang untuk melihat apakah konfigurasi default cocok untuk database MySQL.

Sebenarnya, aplikasi tidak akan bisa dimulai, karena SQLSyntaxErrorException . Ini sebenarnya masuk akal; seperti yang kami katakan, sebagian besar konfigurasi otomatis default cocok untuk 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

Seperti yang bisa kita lihat, pendekatan ini jauh lebih sederhana daripada harus membuat UserDetailService kita sendiriimplementasi, yang menyiratkan proses yang sulit; membuat entitas dan kelas yang menerapkan antarmuka UserDetail dan menambahkan repositori ke proyek kami.

Kekurangannya, tentu saja, sedikit fleksibilitas yang ditawarkan ketika database atau logika kita berbeda dari strategi default yang disediakan oleh solusi Spring Security.

Terakhir, kita dapat melihat contoh lengkap di repositori GitHub kita. Kami bahkan menyertakan contoh menggunakan PostgreSQL yang tidak kami tunjukkan dalam tutorial ini, hanya untuk menyederhanakannya.

Ketekunan bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya