Сохранить пользователя после успешной аутентификации
Я настроил 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);
}
}
Проблемы:
- По какой-то причине мой фильтр выполняется дважды за запрос. Я вижу 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()
метод выполняется до завершения вашего будущего вызова.