Spring Security 5 untuk Aplikasi Reaktif

1. Pengenalan

Dalam artikel ini, kita akan meneroka ciri baru kerangka Spring Security 5 untuk mendapatkan aplikasi reaktif. Keluaran ini diselaraskan dengan Spring 5 dan Spring Boot 2.

Dalam artikel ini, kita tidak akan membincangkan secara terperinci mengenai aplikasi reaktif itu sendiri, yang merupakan ciri baru dari rangka Spring 5. Pastikan anda membaca artikel Intro to Reactor Core untuk maklumat lebih lanjut.

2. Persediaan Maven

Kami akan menggunakan permulaan Spring Boot untuk memacu projek kami bersama dengan semua pergantungan yang diperlukan.

Penyediaan asas memerlukan perisytiharan induk, pemula web, dan pergantungan keselamatan pemula. Kami juga memerlukan kerangka ujian Spring Security:

 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 boleh melihat versi permulaan starter keselamatan Spring Boot di Maven Central.

3. Penyediaan Projek

3.1. Bootstrapping Aplikasi Reaktif

Kami tidak akan menggunakan konfigurasi @SpringBootApplication standard tetapi sebaliknya, konfigurasikan pelayan web berasaskan Netty. Netty adalah rangka kerja berasaskan NIO yang tidak segerak yang merupakan asas yang baik untuk aplikasi reaktif.

The @EnableWebFlux anotasi membolehkan standard konfigurasi Spring Web reaktif untuk permohonan:

@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, kami membuat konteks aplikasi baru dan menunggu Netty ditutup dengan memanggil rantai .onClose (). Block () pada konteks Netty.

Setelah Netty dimatikan, konteksnya akan ditutup secara automatik menggunakan blok cubaan dengan sumber .

Kami juga perlu membuat pelayan HTTP berasaskan Netty, pengendali untuk permintaan HTTP, dan penyesuai antara pelayan dan pengendali:

@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 Keselamatan Musim Bunga

Untuk konfigurasi Spring Security asas kami, kami akan membuat kelas konfigurasi - SecurityConfig .

Untuk mengaktifkan sokongan WebFlux di Spring Security 5, kami hanya perlu menentukan anotasi @EnableWebFluxSecurity :

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

Sekarang kita dapat memanfaatkan ServerHttpSecurity kelas untuk membina konfigurasi keselamatan kita.

Kelas ini adalah ciri baru Spring 5. Ia serupa dengan pembangun HttpSecurity , tetapi hanya diaktifkan untuk aplikasi WebFlux.

The ServerHttpSecurity sudah dipratatarajahkan dengan beberapa mungkir waras, supaya kita boleh skip konfigurasi ini sepenuhnya. Tetapi untuk permulaan, kami akan memberikan konfigurasi minimum berikut:

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

Kami juga memerlukan perkhidmatan perincian pengguna. Spring Security memberi kami pembangun pengguna palsu dan pelaksanaan dalam perkhidmatan perincian pengguna dalam ingatan:

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

Oleh kerana kita berada di tanah reaktif, perkhidmatan perincian pengguna juga harus reaktif. Sekiranya kita melihat antara muka ReactiveUserDetailsService , kita akan melihat bahawa kaedah findByUsername - nya sebenarnya mengembalikan penerbit Mono :

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

Sekarang kita boleh menjalankan aplikasi kita dan melihat borang pengesahan asas HTTP biasa.

4. Borang Log Masuk Bergaya

Peningkatan kecil tetapi mencolok dalam Spring Security 5 adalah borang log masuk gaya baru yang menggunakan rangka kerja Bootstrap 4 CSS. Lembaran gaya dalam borang log masuk ke CDN, jadi kami hanya akan melihat peningkatan ketika disambungkan ke Internet.

Untuk menggunakan borang log masuk baru, mari tambahkan kaedah pembangun formLogin () yang sesuai ke pembina ServerHttpSecurity :

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

Sekiranya kita sekarang membuka halaman utama aplikasi, kita akan melihatnya lebih baik daripada bentuk lalai yang biasa kita gunakan sejak versi Spring Security sebelumnya:

Perhatikan bahawa ini bukan borang siap pengeluaran, tetapi ini adalah tali boot aplikasi kami yang baik.

Sekiranya kita sekarang log masuk dan kemudian pergi ke // localhost: 8080 / logout URL, kita akan melihat borang pengesahan log keluar, yang juga digayakan.

5. Keselamatan Pengawal Reaktif

Untuk melihat sesuatu di sebalik borang pengesahan, mari kita laksanakan pengawal reaktif sederhana yang memberi salam kepada pengguna:

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

Setelah log masuk, kita akan melihat ucapan tersebut. Mari tambahkan pengendali reaktif lain yang hanya dapat diakses oleh pentadbir:

@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 peranan ADMIN : dalam perkhidmatan perincian pengguna kami:

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

Kita sekarang boleh menambahkan peraturan pemadan untuk URL pentadbir yang mengharuskan pengguna memiliki kewenangan ROLE_ADMIN .

Perhatikan bahawa kita harus meletakkan pemadan sebelum panggilan rantai .anyExchange () . Panggilan ini berlaku untuk semua URL lain yang belum dilindungi 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.

Log masuk dan kata laluan pengguna ini masing-masing adalah pengguna dan kata laluan , dan peranannya adalah PENGGUNA . Ini tentu saja semuanya boleh dikonfigurasi dengan parameter anotasi @MockWithUser .

Sekarang kita dapat memeriksa bahawa pengguna yang diberi kuasa melihat ucapan:

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

The @WithMockUser anotasi boleh didapati sejak Spring keselamatan 4. Walau bagaimanapun, dalam Spring Keselamatan 5 ia juga dikemaskini untuk menampung titik hujung reaktif dan kaedah.

8. Kesimpulannya

Dalam tutorial ini, kami telah menemui ciri baru dari peluncuran Spring Security 5 yang akan datang, terutama di arena pengaturcaraan reaktif.

Seperti biasa, kod sumber untuk artikel tersebut terdapat di GitHub.