I have set up keycloak with a google identity provider. And I have set up a simple reactive spring-boot web application with spring security and MongoDB. I want to save users after they successfully pass the authorization filter. Here is my security configuration:
@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();
}
}
And here is my filter for saving users:
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);
}
}
Problems:
- For some reason, my filter executes twice per request. I can see 2 log messages about saving new user.
- When I call getAll() endpoint, it does not return the user saved in this request in the filter.
Probably it is not the best way to save users, but I could not find an alternative to successHandler for the resource server with jwt. Please suggest how can I solve those two problems.