Рубрики
Без рубрики

Пружинная защита 5 для реактивных приложений

Быстрый и практический пример функций Spring Security 5 framework для защиты реактивных приложений.

Автор оригинала: baeldung.

1. введение

В этой статье мы рассмотрим новые функции фреймворка Spring Security 5 для защиты реактивных приложений. Этот выпуск выровнен с пружиной 5 и пружинной загрузкой 2.

В этой статье мы не будем вдаваться в подробности о самих реактивных приложениях, что является новой функцией фреймворка Spring 5. Обязательно ознакомьтесь со статьей “Введение в активную зону реактора” для получения более подробной информации.

2. Настройка Maven

Мы будем использовать Spring Boot starters для начальной загрузки нашего проекта вместе со всеми необходимыми зависимостями.

Базовая настройка требует родительского объявления, зависимостей веб-стартера и стартера безопасности. Нам также понадобится платформа тестирования безопасности Spring:


    org.springframework.boot
    spring-boot-starter-parent
    2.4.0
    



    
        org.springframework.boot
        spring-boot-starter-webflux
    
    
        org.springframework.boot
        spring-boot-starter-security
    
    
        org.springframework.security
        spring-security-test
        test
    

Мы можем проверить текущую версию Spring Boot security starter в Maven Central .

3. Настройка проекта

3.1. Загрузка реактивного приложения

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

Аннотация @EnableWebFlux включает стандартную конфигурацию Spring Web Reactive для приложения:

@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() chain в контексте 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

Для нашей базовой конфигурации безопасности Spring мы создадим класс конфигурации – SecurityConfig .

Чтобы включить поддержку WebFlux в Spring Security 5, нам нужно только указать аннотацию @EnableWebFluxSecurity :

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

Теперь мы можем воспользоваться преимуществами класса Безопасность Http Сервера чтобы построить нашу конфигурацию безопасности.

Этот класс является новой функцией Spring 5. Он похож на Http Security builder, но он включен только для приложений веб-потока.

Безопасность Http сервера уже предварительно настроена с некоторыми разумными значениями по умолчанию, поэтому мы могли бы полностью пропустить эту конфигурацию. Но для начала мы предоставим следующую минимальную конфигурацию:

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

Поскольку мы находимся в реактивной стране, служба userdetailsservice также должна быть реактивной. Если мы проверим интерфейс Reactive UserDetailsService , мы увидим, что его метод findByUsername фактически возвращает Mono publisher:

public interface ReactiveUserDetailsService {

    Mono findByUsername(String username);
}

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

4. Стилизованная Форма Входа в систему

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

Чтобы использовать новую форму входа в систему, давайте добавим соответствующий метод form Login() builder в Server Http Security builder:

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

Если мы сейчас откроем главную страницу приложения, то увидим, что она выглядит намного лучше, чем форма по умолчанию, к которой мы привыкли с предыдущих версий Spring Security:

Обратите внимание, что это не готовая к производству форма, но это хорошая начальная загрузка нашего приложения.

Если мы сейчас войдем в систему, а затем перейдем к http://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));
}

Теперь давайте создадим второго пользователя с ролью ADMIN : в нашем сервисе userdetailsservice:

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

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

Обратите внимание, что мы должны поставить совпадения перед вызовом цепочки .any Exchange () . Этот вызов применяется ко всем другим URL-адресам, которые еще не были охвачены другими совпадениями:

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

Если мы теперь войдем в систему с user или admin , мы увидим, что они оба соблюдают первоначальное приветствие, поскольку мы сделали его доступным для всех аутентифицированных пользователей.

Но только администратор пользователь может перейти в http://localhost:8080/admin URL и увидеть ее приветствие .

6. Безопасность Реактивного Метода

Мы видели, как мы можем защитить URL-адреса, но как насчет методов?

Чтобы включить безопасность на основе методов для реактивных методов, нам нужно только добавить аннотацию @EnableReactiveMethodSecurity в наш класс SecurityConfig :

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

Теперь давайте создадим службу реактивных приветствий со следующим контентом:

@Service
public class GreetService {

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

Мы можем ввести его в контроллер, перейдите в http://localhost:8080/greetService и убедитесь, что это действительно работает:

@RestController
public class GreetController {

    private GreetService greetService

    @GetMapping("/greetService")
    public Mono greetService() {
        return greetService.greet();
    }

    // standard constructors...
}

Но если мы теперь добавим аннотацию @PreAuthorize к методу службы с ролью ADMIN , то URL-адрес большой службы не будет доступен обычному пользователю:

@Service
public class GreetService {

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

7. Издевательство над пользователями в тестах

Давайте проверим, насколько легко протестировать наше приложение с реактивной пружиной.

Во – первых, мы создадим тест с введенным контекстом приложения:

@ContextConfiguration(classes = SpringSecurity5Application.class)
public class SecurityTest {

    @Autowired
    ApplicationContext context;

    // ...
}

Теперь мы настроим простой клиент реактивного веб-тестирования, который является особенностью тестовой платформы Spring 5:

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

Это позволяет нам быстро проверить, что неавторизованный пользователь перенаправлен с главной страницы нашего приложения на страницу входа в систему:

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

Если мы теперь добавим @@Smokewithus аннотация к методу тестирования мы можем предоставить аутентифицированного пользователя для этого метода.

Логин и пароль этого пользователя будут user и password соответственно, а роль – 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 .