Выбрать главу

@Configuration

@EnableWebFluxSecurity

public class SecurityConfig {

   @Bean

   public SecurityWebFilterChain securityWebFilterChain(

            ServerHttpSecurity http) {

      return http

         .authorizeExchange()

         .pathMatchers("/design", "/orders").hasAuthority("USER")

         .anyExchange().permitAll()

         .and()

         .build();

   }

}

Как вы можете видеть, есть много того, что знакомо, но в то же время отличающегося. Вместо @EnableWebSecurity этот новый класс конфигурации аннотируется с помощью @EnableWebFluxSecurity. Более того, класс конфигурации не расширяет WebSecurityConfigurerAdapter или любой другой базовый класс вообще. Поэтому он также не переопределяет методы configure().

Вместо метода configure() объявляется компонент типа SecurityWebFilterChain с помощью метода securityWebFilterChain(). Тело securityWebFilterChain() не сильно отличается от предыдущих конфигураций метода configure(), но есть некоторые тонкие изменения.

Прежде всего, конфигурация объявляется с использованием заданного объекта ServerHttpSecurity вместо объекта HttpSecurity. Используя данный ServerHttpSecurity, вы можете вызвать authorizeExchange(), который примерно эквивалентен authorizeRequests(), чтобы задать безопасность на уровне запросов.

ПРИМЕЧАНИЕ ServerHttpSecurity является новинкой в Spring Security 5 и является реактивным аналогом HttpSecurity.

При сопоставлении путей вы все равно можете использовать подстановочные пути в стиле Ant, но делайте это с помощью метода pathMatchers() вместо antMatchers(). И для удобства вам больше не нужно указывать универсальный путь в Ant-стиле для /**, потому что anyExchange() возвращает все, что вам нужно.

Наконец, поскольку вы объявляете SecurityWebFilterChain как bean, а не переопределяете метод фрэймворка, вы должны вызвать метод build(), чтобы собрать все правила безопасности в возвращаемом SecurityWebFilterChain.

Помимо этих небольших различий, настройка веб-безопасности ничем не отличается для Spring WebFlux от Spring MVC. Но как насчет пользовательских данных?

11.5.2 Конфигурирование службы реактивных данных пользователя

Расширяя WebSecurityConfigurerAdapter, вы переопределяете один метод configure() для объявления правил веб-безопасности и другой метод configure() для настройки логики аутентификации, обычно путем определения объекта UserDetails. В качестве напоминания о том, как это выглядит, рассмотрим следующий переопределенный метод configure(), который использует внедренный объект UserRepository в анонимной реализации UserDetailsService для поиска пользователя по имени пользователя:

@Autowired

UserRepository userRepo;

@Override

protected void

      configure(AuthenticationManagerBuilder auth)

      throws Exception {

   auth

      .userDetailsService(new UserDetailsService() {

         @Override

         public UserDetails loadUserByUsername(String username)

               throws UsernameNotFoundException {

            User user = userRepo.findByUsername(username)

            if (user == null) {

               throw new UsernameNotFoundException(

                  username " + not found")

            }

            return user.toUserDetails();

         }

   });

}

В этой нереактивной конфигурации вы переопределяете единственный метод, требуемый UserDetailsService, loadUserByUsername(). Внутри этого метода вы используете внедренный UserRepository для поиска пользователя по заданному имени пользователя. Если имя не найдено, вы бросаете исключение UsernameNotFoundException. Но если он найден, то вы вызываете вспомогательный метод toUserDetails() для возврата результирующего объекта UserDetails.

В реактивной конфигурации безопасности вы не переопределяете метод configure(). Вместо этого вы объявляете bean-компонент ReactiveUserDetailsService. ReactiveUserDetailsService является реактивным эквивалентом UserDetailsService. Как и UserDetailsService, ReactiveUserDetailsService требует реализации только одного метода. В частности, метод findByUsername() возвращает Mono<userDetails> вместо необработанного объекта UserDetails.

В следующем примере объявляется, что bean-компонент ReactiveUserDetailsService использует внедренный UserRepository, который предположительно является реактивным Spring Data репозиторием (о котором мы поговорим подробнее в следующей главе):

@Service

public ReactiveUserDetailsService userDetailsService(

      UserRepository userRepo) {

   return new ReactiveUserDetailsService() {

      @Override

      public Mono<UserDetails> findByUsername(String username) {

         return userRepo.findByUsername(username)

            .map(user -> {

               return user.toUserDetails();

            });

      }

   };

}

Здесь Mono<UserDetails> возвращается по мере необходимости, но метод UserRepository.findByUsername() возвращает Mono<User>. Поскольку это Mono, вы можете вызвать операции, такие как map(), чтобы преобразовать Mono<User> в Mono<UserDetails>.

В этом случае операция map() применяется с лямбда-выражением, которое вызывает вспомогательный метод toUserDetails() для объекта User, опубликованного Mono. Это преобразует User в UserDetails. Как следствие, операция .map() возвращает Mono<UserDetails>, который является именно тем, что требуется для ReactiveUserDetailsService.findByUsername().