Keamanan Musim Semi 5 untuk Aplikasi Reaktif

1. Perkenalan

Dalam artikel ini, kita akan menjelajahi fitur-fitur baru dari framework Spring Security 5 untuk mengamankan aplikasi reaktif. Rilis ini sejalan dengan Spring 5 dan Spring Boot 2.

Pada artikel ini, kami tidak akan membahas detail tentang aplikasi reaktif itu sendiri, yang merupakan fitur baru dari framework Spring 5. Pastikan untuk membaca artikel Intro to Reactor Core untuk lebih jelasnya.

2. Pengaturan Maven

Kami akan menggunakan permulaan Spring Boot untuk mem-bootstrap proyek kami bersama dengan semua dependensi yang diperlukan.

Penyiapan dasar memerlukan deklarasi induk, web starter, dan dependensi starter keamanan. Kami juga membutuhkan kerangka kerja pengujian Keamanan Musim Semi:

 org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE     org.springframework.boot spring-boot-starter-webflux   org.springframework.boot spring-boot-starter-security   org.springframework.security spring-security-test test  

Kami dapat memeriksa versi terbaru dari starter keamanan Spring Boot di Maven Central.

3. Pengaturan Proyek

3.1. Bootstrap Aplikasi Reaktif

Kami tidak akan menggunakan konfigurasi standar @SpringBootApplication , melainkan mengkonfigurasi server web berbasis Netty. Netty adalah kerangka kerja berbasis NIO asinkron yang merupakan dasar yang baik untuk aplikasi reaktif.

The @EnableWebFlux penjelasan memungkinkan standar konfigurasi Spring Web Reaktif untuk aplikasi:

@ComponentScan(basePackages = {"com.baeldung.security"}) @EnableWebFlux public class SpringSecurity5Application { public static void main(String[] args) { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( SpringSecurity5Application.class)) { context.getBean(NettyContext.class).onClose().block(); } }

Di sini, kita membuat konteks aplikasi baru dan menunggu Netty ditutup dengan memanggil rantai .onClose (). Block () pada konteks Netty.

Setelah Netty dimatikan, konteks akan secara otomatis ditutup menggunakan blok coba-dengan-sumber daya .

Kita juga perlu membuat server HTTP berbasis Netty, penangan untuk permintaan HTTP, dan adaptor antara server dan penangan:

@Bean public NettyContext nettyContext(ApplicationContext context) { HttpHandler handler = WebHttpHandlerBuilder .applicationContext(context).build(); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); HttpServer httpServer = HttpServer.create("localhost", 8080); return httpServer.newHandler(adapter).block(); }

3.2. Kelas Konfigurasi Keamanan Musim Semi

Untuk konfigurasi Spring Security dasar kita, kita akan membuat kelas konfigurasi - SecurityConfig .

Untuk mengaktifkan dukungan WebFlux di Spring Security 5, kita hanya perlu menentukan anotasi @EnableWebFluxSecurity :

@EnableWebFluxSecurity public class SecurityConfig { // ... }

Sekarang kita dapat memanfaatkan kelas ServerHttpSecurity untuk membangun konfigurasi keamanan kita.

Kelas ini adalah fitur baru pada Spring 5. Ini mirip dengan pembuat HttpSecurity , tetapi hanya diaktifkan untuk aplikasi WebFlux.

The ServerHttpSecurity sudah dikonfigurasikan dengan beberapa default waras, sehingga kami bisa melewatkan konfigurasi ini sepenuhnya. Namun sebagai permulaan, kami akan memberikan konfigurasi minimal berikut:

@Bean public SecurityWebFilterChain securitygWebFilterChain( ServerHttpSecurity http) { return http.authorizeExchange() .anyExchange().authenticated() .and().build(); }

Selain itu, kami membutuhkan layanan detail pengguna. Spring Security memberi kami pembuat pengguna tiruan yang nyaman dan implementasi dalam memori dari layanan detail pengguna:

@Bean public MapReactiveUserDetailsService userDetailsService() { UserDetails user = User .withUsername("user") .password(passwordEncoder().encode("password")) .roles("USER") .build(); return new MapReactiveUserDetailsService(user); }

Karena kita berada di wilayah reaktif, layanan detail pengguna juga harus reaktif. Jika kita memeriksa antarmuka ReactiveUserDetailsService , kita akan melihat bahwa metode findByUsername benar-benar mengembalikan penerbit Mono :

public interface ReactiveUserDetailsService { Mono findByUsername(String username); }

Sekarang kita dapat menjalankan aplikasi kita dan mengamati formulir otentikasi dasar HTTP biasa.

4. Formulir Login Bergaya

Peningkatan kecil namun mencolok di Spring Security 5 adalah formulir login bergaya baru yang menggunakan framework CSS Bootstrap 4. Stylesheet di formulir login tertaut ke CDN, jadi kami hanya akan melihat peningkatannya saat tersambung ke Internet.

Untuk menggunakan formulir login baru, mari tambahkan metode pembuat formLogin () yang sesuai ke pembuat ServerHttpSecurity :

public SecurityWebFilterChain securitygWebFilterChain( ServerHttpSecurity http) { return http.authorizeExchange() .anyExchange().authenticated() .and().formLogin() .and().build(); }

Jika sekarang kita membuka halaman utama aplikasi, kita akan melihat bahwa itu terlihat jauh lebih baik daripada bentuk default yang biasa kita gunakan sejak versi Spring Security sebelumnya:

Perhatikan bahwa ini bukan formulir siap produksi, tetapi ini adalah bootstrap yang baik untuk aplikasi kita.

Jika sekarang kita masuk dan kemudian pergi ke // localhost: 8080 / URL logout, kita akan melihat formulir konfirmasi logout, yang juga bergaya.

5. Keamanan Kontroler Reaktif

Untuk melihat sesuatu di balik formulir otentikasi, mari implementasikan pengontrol reaktif sederhana yang menyapa pengguna:

@RestController public class GreetController { @GetMapping("/") public Mono greet(Mono principal) { return principal .map(Principal::getName) .map(name -> String.format("Hello, %s", name)); } }

Setelah masuk, kita akan melihat sapaannya. Mari tambahkan penangan reaktif lain yang hanya dapat diakses oleh admin:

@GetMapping("/admin") public Mono greetAdmin(Mono principal) { return principal .map(Principal::getName) .map(name -> String.format("Admin access: %s", name)); }

Sekarang mari buat pengguna kedua dengan peran ADMIN : di layanan detail pengguna kami:

UserDetails admin = User.withDefaultPasswordEncoder() .username("admin") .password("password") .roles("ADMIN") .build();

Sekarang kita dapat menambahkan aturan matcher untuk URL admin yang mengharuskan pengguna memiliki otoritas ROLE_ADMIN .

Perhatikan bahwa kita harus meletakkan matcher sebelum panggilan berantai .anyExchange () . Panggilan ini berlaku untuk semua URL lain yang belum tercakup oleh pencocokan lain:

return http.authorizeExchange() .pathMatchers("/admin").hasAuthority("ROLE_ADMIN") .anyExchange().authenticated() .and().formLogin() .and().build();

If we now log in with user or admin, we'll see that they both observe initial greeting, as we've made it accessible for all authenticated users.

But only the admin user can go to the //localhost:8080/admin URL and see her greeting.

6. Reactive Method Security

We've seen how we can secure the URLs, but what about methods?

To enable method-based security for reactive methods, we only need to add the @EnableReactiveMethodSecurity annotation to our SecurityConfig class:

@EnableWebFluxSecurity @EnableReactiveMethodSecurity public class SecurityConfig { // ... }

Now let's create a reactive greeting service with the following content:

@Service public class GreetService { public Mono greet() { return Mono.just("Hello from service!"); } }

We can inject it into the controller, go to //localhost:8080/greetService and see that it actually works:

@RestController public class GreetController { private GreetService greetService @GetMapping("/greetService") public Mono greetService() { return greetService.greet(); } // standard constructors... }

But if we now add the @PreAuthorize annotation on the service method with the ADMIN role, then the greet service URL won't be accessible to a regular user:

@Service public class GreetService { @PreAuthorize("hasRole('ADMIN')") public Mono greet() { // ... }

7. Mocking Users in Tests

Let's check out how easy it is to test our reactive Spring application.

First, we'll create a test with an injected application context:

@ContextConfiguration(classes = SpringSecurity5Application.class) public class SecurityTest { @Autowired ApplicationContext context; // ... }

Now we'll set up a simple reactive web test client, which is a feature of the Spring 5 test framework:

@Before public void setup() { this.rest = WebTestClient .bindToApplicationContext(this.context) .configureClient() .build(); }

This allows us to quickly check that the unauthorized user is redirected from the main page of our application to the login page:

@Test public void whenNoCredentials_thenRedirectToLogin() { this.rest.get() .uri("/") .exchange() .expectStatus().is3xxRedirection(); }

If we now add the @MockWithUser annotation to a test method, we can provide an authenticated user for this method.

Login dan sandi pengguna ini masing-masing adalah pengguna dan sandi , dan perannya adalah PENGGUNA . Ini, tentu saja, semua dapat dikonfigurasi dengan parameter anotasi @MockWithUser .

Sekarang kita dapat memeriksa apakah pengguna yang berwenang melihat salam:

@Test @WithMockUser public void whenHasCredentials_thenSeesGreeting() { this.rest.get() .uri("/") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello, user"); }

The @WithMockUser penjelasan tersedia sejak Spring Security 4. Namun, di Spring Security 5 itu juga diperbarui untuk menutupi endpoint reaktif dan metode.

8. Kesimpulan

Dalam tutorial ini, kami telah menemukan fitur-fitur baru dari rilis Spring Security 5 yang akan datang, terutama di arena pemrograman reaktif.

Seperti biasa, kode sumber artikel tersedia di GitHub.