Spring Security 5 для реактивних додатків

1. Вступ

У цій статті ми розглянемо нові функції середовища Spring Security 5 для захисту реактивних програм. Цей випуск суміщений із Spring 5 та Spring Boot 2.

У цій статті ми не будемо вдаватися в подробиці про самі реактивні програми, що є новою функцією фреймворку Spring 5. Обов’язково перегляньте статтю Вступ до ядра реактора, щоб отримати докладнішу інформацію.

2. Налаштування Maven

Ми використаємо початкові програми Spring Boot для запуску нашого проекту разом із усіма необхідними залежностями.

Для базового налаштування потрібні батьківська декларація, залежності від веб-стартера та стартера безпеки. Нам також знадобиться рамка тесту 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  

Ми можемо перевірити поточну версію запуску безпеки Spring Boot у Maven Central.

3. Налаштування проекту

3.1. Початкове завантаження реактивного додатка

Ми не будемо використовувати стандартну конфігурацію @SpringBootApplication, а замість цього налаштуємо веб-сервер на базі Netty. Netty - це асинхронний фреймворк на основі NIO, який є хорошою основою для реактивних додатків.

@EnableWebFlux анотації дозволяють стандартну конфігурацію Spring Web Реактивної для програми:

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

Тут ми створюємо новий контекст програми і чекаємо, поки Netty вимкнеться, викликаючи ланцюжок .onClose (). Block () у контексті Netty.

Після вимкнення Netty контекст буде автоматично закрито за допомогою блоку try-with-resources .

Нам також потрібно буде створити HTTP-сервер на основі Netty, обробник для HTTP-запитів та адаптер між сервером та обробником:

@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. Весняний клас конфігурації безпеки

Для нашої базової конфігурації Spring Security ми створимо клас конфігурації - SecurityConfig .

Щоб увімкнути підтримку WebFlux у Spring Security 5, нам потрібно лише вказати анотацію @EnableWebFluxSecurity :

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

Тепер ми можемо скористатися класом ServerHttpSecurity для створення нашої конфігурації безпеки.

Цей клас є новою функцією Spring 5. Він схожий на HttpSecurity builder, але він увімкнений лише для додатків WebFlux.

ServerHttpSecurity вже попередньо налаштований з деяким розсудливими по замовчуванням, так що ми могли б повністю пропустити цю конфігурацію. Але для початку ми надамо таку мінімальну конфігурацію:

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

Крім того, нам знадобиться служба інформації про користувача. Spring Security надає нам зручний імітаційний конструктор користувачів та реалізацію в пам’яті служби даних про користувача:

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

Оскільки ми знаходимося в реактивній зоні, служба інформації про користувача також повинна реагувати. Якщо ми перевіримо інтерфейс ReactiveUserDetailsService , то побачимо, що метод findByUsername насправді повертає видавця Mono :

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

Тепер ми можемо запустити наш додаток і спостерігати за звичайною базовою формою автентифікації HTTP.

4. Стилізована форма для входу

Невелике, але вражаюче вдосконалення Spring Security 5 - це нова стилізована форма для входу, яка використовує фреймворк Bootstrap 4 CSS. Таблиці стилів у формі для входу посилаються на CDN, тому покращення ми побачимо лише при підключенні до Інтернету.

Щоб використовувати нову форму для входу, додамо відповідний метод конструктора formLogin () до конструктора ServerHttpSecurity :

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

Якщо ми зараз відкриємо головну сторінку програми, ми побачимо, що вона виглядає набагато краще, ніж форма за замовчуванням, до якої ми звикли з попередніх версій Spring Security:

Зверніть увагу, що це не форма, готова до виробництва, але це хороший завантажувальний шаблон нашого додатку.

Якщо ми зараз увійдемо в систему, а потім перейдемо до // localhost: 8080 / logout URL, ми побачимо форму підтвердження виходу, яка також стилізована.

5. Безпека реактивного контролера

Щоб побачити щось за формою автентифікації, давайте реалізуємо простий реактивний контролер, який вітає користувача:

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

Після входу ми побачимо привітання. Давайте додамо ще один реактивний обробник, який буде доступний лише адміністратору:

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

Тепер давайте створимо другого користувача з роллю АДМІНІСТРАТОР : у нашій службі інформації про користувача:

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

Тепер ми можемо додати правило збігу для URL-адреси адміністратора, яке вимагає, щоб користувач мав повноваження ROLE_ADMIN .

Зверніть увагу, що ми повинні поставити збіги перед ланцюговим викликом .anyExchange () . Цей дзвінок застосовується до всіх інших URL-адрес, які ще не були охоплені іншими відповідниками:

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.

Логін та пароль цього користувача будуть відповідно користувачем та паролем , а роль USER . Це, звичайно, все можна налаштувати за допомогою параметрів анотації @MockWithUser .

Тепер ми можемо перевірити, чи авторизований користувач бачить привітання:

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

@WithMockUser анотації доступні , починаючи з Spring Security 4. Тим НЕ менше, в Spring Security 5 було також оновлено , щоб покрити реакційно кінцеві точки і методу.

8. Висновок

У цьому підручнику ми відкрили нові функції майбутнього випуску Spring Security 5, особливо на арені реактивного програмування.

Як завжди, вихідний код статті доступний на GitHub.