Почему у меня двойной запрос к конечной точке аутентификации, когда я использую Spring Security WebFlux

#java #spring-boot #security #spring-webflux

#java #spring-boot #Безопасность #spring-webflux

Вопрос:

Я пытаюсь создать простую схему аутентификации с реактивным подходом. Я создал проект с нуля с зависимостями от реактивных компонентов и безопасности. Введен файл конфигурации, в котором я настраиваю диспетчер аутентификации и репозиторий контекста безопасности. Проблема в том, что я замечаю, что Mono, введенный в контроллер, инициирует двойные запросы к конечной точке «входа».

Почему это происходит и как это предотвратить?

Вот код конфигурации:

 
@Configuration
@EnableWebFluxSecurity
public class WebFluxSecurityConfiguration {


    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private SecurityContextRepository securityContextRepository;


    @Bean
    public SecurityWebFilterChain securityWebFilterChain(
            ServerHttpSecurity http) {
        return http
                .csrf().disable()
                .cors().disable()
                .httpBasic().disable()
                .logout().disable()
                .formLogin().disable()
                .authorizeExchange()
                .pathMatchers("/login").permitAll()
                .anyExchange().authenticated()
                .and()
                .authenticationManager(authenticationManager)
                .securityContextRepository(securityContextRepository)
                .build();
    }
}

  

Вот менеджер аутентификации

 @Component
public class AuthenticationManager implements ReactiveAuthenticationManager {
    private final WebClient webClient;

    public AuthenticationManager(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("http://localhost:8080/login")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .build();
    }
    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        return webClient.post()
                .header("Authorization","Bearer bla-bla")
                .retrieve()
                .bodyToMono(String.class)
                .map(r->new AuthenticatedUser());
    }
}
  

И вот репозиторий контекста безопасности

 @Component
public class SecurityContextRepository implements ServerSecurityContextRepository {

    private static final String TOKEN_PREFIX = "Bearer ";

    private AuthenticationManager authenticationManager;

    List<PathPattern> pathPatternList;

    public SecurityContextRepository(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        PathPattern pathPattern1 = new PathPatternParser().parse("/login");
        pathPatternList = new ArrayList<>();
        pathPatternList.add(pathPattern1);
    }

    @Override
    public Mono load(ServerWebExchange swe) {

        ServerHttpRequest request = swe.getRequest();

        RequestPath path = request.getPath();
        if (pathPatternList.stream().anyMatch(pathPattern -> pathPattern.matches(path.pathWithinApplication()))) {
            System.out.println(path.toString()   " path excluded");
            return Mono.empty();
        }
        System.out.println("executing logic for "   path.toString()   " path");

        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        String authToken = null;
        //test
        authHeader = "Bearer bla-bla";
        //~test
        if (authHeader != null amp;amp; authHeader.startsWith(TOKEN_PREFIX)) {
            authToken = authHeader.replace(TOKEN_PREFIX, "");
        }else {
            System.out.println("couldn't find bearer string, will ignore the header.");
        }
        if (authToken != null) {
            Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
            return this.authenticationManager.authenticate(auth).map((authentication) -> new SecurityContextImpl(authentication));
        } else {
            return Mono.empty();
        }
    }

    @Override
    public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {

        return null;
    }
}
  

ссылка на репозиторий полного проекта

Комментарии:

1. Почему вы устанавливаете токен как имя пользователя, так и пароль? И почему вы не используете встроенный фильтр jwt в spring 5?

2. Это всего лишь синтетическое приложение, в котором я проверяю гипотезу. Цель состоит в том, чтобы реализовать авторизацию в реактивном приложении с помощью стороннего сервера авторизации. Клиент поставляется с некоторым токеном, с помощью которого я могу извлечь профиль пользователя с сервера авторизации, а затем реализовать фильтрацию запроса. Проблема этого приложения заключается в том, что — когда я пытаюсь получить участника в контроллере — webclient отправляет второй запрос на сервер авторизации. И как этого избежать?

3. и затем я снова спрошу, поскольку вы не ответили на мой вопрос, почему вы не используете встроенную функциональность JWTs в spring 5, у нее есть все необходимые возможности настройки?

4. Я думаю, мне нужно провести исследование реактивного стека и предоставить больше встроенной функциональности. Спасибо!

Ответ №1:

Быстрым решением в этом конкретном случае является реализация кэширования результата WebClient. Итак, внутри метода post для сервера авторизации для извлечения профиля пользователя я только что ввел метод cache() .

    return webClient.post()
                .header("Authorization","Bearer bla-bla")
                .retrieve()
                .bodyToMono(String.class)
                .cache()
                .map(r->new AuthenticatedUser());

  

Это помогло избежать повторных запросов к конечной точке авторизации во время запроса.