Сохранить пользователя после успешной аутентификации

Я настроил keycloak с поставщиком удостоверений Google. И я настроил простое реактивное веб-приложение с пружинной загрузкой с Spring Security и MongoDB. Я хочу сохранить пользователей после того, как они успешно пройдут фильтр авторизации. Вот моя конфигурация безопасности:

      @EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@Slf4j
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final UserSavingFilter userSavingFilter;

    @Bean
    SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange()
                .anyExchange().authenticated()
                .and()
                .addFilterAfter(userSavingFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                .oauth2ResourceServer()
                .jwt();

        return http.build();
    }

}

А вот мой фильтр для сохранения пользователей:

      public class UserSavingFilter implements WebFilter {

    private final ObjectMapper objectMapper;

    private final UserService userService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Base64.Decoder decoder = Base64.getUrlDecoder();
        var authHeader = getAuthHeader(exchange);
        if (authHeader == null) {
            return chain.filter(exchange);
        }
        var encodedPayload = authHeader.split("Bearer ")[1].split("\\.")[1];
        var userDetails = convertToMap(new String(decoder.decode(encodedPayload)));
        saveUserIfNotPresent(userDetails);

        return chain.filter(exchange);
    }

    @SneakyThrows
    private void saveUserIfNotPresent(Map<String, Object> map) {
        var userEmail = String.valueOf(map.get("email"));

        var userPresent = userService.existsByEmail(userEmail).toFuture().get();

        if (userPresent) {
            return;
        }

        log.info("Saving new user with email: {}", userEmail);

        var user = new User();
        user.setEmail(userEmail);
        user.setFirstName(String.valueOf(map.get("given_name")));
        user.setLastName(String.valueOf(map.get("family_name")));
        userService.save(user).subscribe(User::getId);
    }

    @SuppressWarnings("java:S2259")
    private String getAuthHeader(ServerWebExchange exchange) {
        var authHeaders = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION);
        if (authHeaders == null) {
            return null;
        }
        return authHeaders.get(0);
    }

    @SneakyThrows
    private Map<String, Object> convertToMap(String payloadJson) {
        return objectMapper.readValue(payloadJson,Map.class);
    }
}

Проблемы:

  1. По какой-то причине мой фильтр выполняется дважды за запрос. Я вижу 2 сообщения журнала о сохранении нового пользователя.
  2. Когда я вызываю конечную точку getAll(), она не возвращает пользователя, сохраненного в этом запросе, в фильтре.

Возможно, это не лучший способ сохранить пользователей, но альтернативы successHandler для сервера ресурсов с jwt я не нашел. Пожалуйста, предложите, как я могу решить эти две проблемы.

1 ответ

По какой-то причине ваш фильтр аннотирован@Component? Это может объяснить, почему он вызывается дважды, поскольку Spring Boot автоматически регистрирует любой bean-компонент, который является фильтром, с контейнером сервлета (см. документацию ).

Таким образом, вы можете настроить компонент регистрации, чтобы отключить его:

      @Bean
public FilterRegistrationBean<UserSavingFilter> disableUserSavingFilter(final UserSavingFilter filter) {
    final FilterRegistrationBean<UserSavingFilter> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.setFilter(filter);
    filterRegistrationBean.setEnabled(false);
    return filterRegistrationBean;
}

По умолчанию пользовательские фильтрующие компоненты без информации о порядке автоматически добавляются в основную цепочку фильтров сервлета в самой последней позиции (фактически с самым низким приоритетом, как если бы вы применяли аннотацию порядка по умолчанию@Order(Ordered.LOWEST_PRECEDENCE), см. Порядок ).

На уровне отладки вы должны увидеть в журналах положение фильтров при их добавлении в цепочку, что-то вроде:

... на позиции 4 из 4 в дополнительной цепочке фильтров; фильтр активации:UserSavingFilter

Что касается вашей второй проблемы, если вы уверены, что пользователь действительно сохранен (т.е. вы нашли его в базе данных впоследствии), то действительно это может быть просто потому, что вашgetAll()метод выполняется до завершения вашего будущего вызова.

Другие вопросы по тегам