12.5.2 Configuring a reactive user details service
When extending WebSecurityConfigurerAdapter, you override one configure() method to declare web security rules and another configure() method to configure authentication logic, typically by defining a UserDetails object. As a reminder of what this looks like, consider the following overridden configure() method that uses an injected UserRepository object in an anonymous implementation of UserDetailsService to look up a user by username:
@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();
}
});
}In this nonreactive configuration, you override the only method required by UserDetailsService: loadUserByUsername(). Inside of that method, you use the given UserRepository to look up the user by the given username. If the name isn’t found, you throw a UsernameNotFoundException. But if it’s found, then you call a helper method, toUserDetails(), to return the resulting UserDetails object.
In a reactive security configuration, you don’t override a configure() method. Instead, you declare a ReactiveUserDetailsService bean. ReactiveUserDetailsService is the reactive equivalent to UserDetailsService. Like UserDetailsService, ReactiveUserDetailsService requires implementation of only a single method. Specifically, the findByUsername() method returns a Mono<UserDetails> instead of a raw UserDetails object.
In the following example, the ReactiveUserDetailsService bean is declared to use a given UserRepository, which is presumed to be a reactive Spring Data repository (which we’ll talk more about in the next chapter):
@Service
@Bean
public ReactiveUserDetailsService userDetailsService(
UserRepository userRepo) {
return new ReactiveUserDetailsService() {
@Override
public Mono<UserDetails> findByUsername(String username) {
return userRepo.findByUsername(username)
.map(user -> {
return user.toUserDetails();
});
}
};
}Here, a Mono<UserDetails> is returned as required, but the UserRepository.findByUsername() method returns a Mono<User>. Because it’s a Mono, you can chain operations on it, such as a map() operation to map the Mono<User> to a Mono<UserDetails>.
In this case, the map() operation is applied with a lambda that calls the helper toUserDetails() method on the User object published by the Mono. This converts the User to a UserDetails. As a consequence, the .map() operation returns a Mono<UserDetails>, which is precisely what the ReactiveUserDetailsService.findByUsername() requires. If findByUsername() can’t find a matching user, then the Mono returned will be empty, indicating no match and resulting in a failure to authenticate.
