Learn Spring Security
Contents
Authentication with AppUser
So far we have authenticated using the default user and the random password provided by Spring Security. But as in any web applications we want to authenticate using our application users, which were created in the database by AppDataInitialiser when the application bootstraps. Let's see how we can do this using one of the implementations of UserDetailsService provided by Spring Security.
InMemoryUserDetailsManager
Spring Security uses UserDetailsService as the core interface to load user-specific data by username, and it requires only one read-only method.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException;
}
InMemoryUserDetailsManager is one of the implementations provided by Spring Security. It is a non-persistant implementation backed by an in-memory map mainly intended for testing and demonstration purposes only. It accepts a collection of UserDetails objects, which is the same as the return type of the above loadUserByUsername() method.
Create UserDetails from database
Before registering InMemoryUserDetailsManager bean, let's create a collection of UserDetails objects. We can do this by fetching all the AppUser records from the database, map them all with User class and return the collection from a @Service method like below.
@Service
public class DbUserDetailsService {
@Autowired
private AppUserRepository appUserRepository;
public List<UserDetails> getAllUserDetails() {
return appUserRepository.findAll()
.stream()
.map(appUser -> User.builder()
.username(appUser.getUsername())
.password(String.format("{noop}%s", appUser.getPassword()))
.authorities(Collections.EMPTY_SET)
.build()
)
.collect(Collectors.toList());
}
}
User is one of the implementations of UserDetails interface with a convenient Builder class. Here we have mapped username and password only and left the authorities as an empty Set as we are focusing only on authenticating application users for now.
We must provide the correct password encoding schema used to encode the passwords stored in the database. We can do this by prefixing each password with the appropriate string literal representing the PasswordEncoder. In our case we have not encrypted the passwords in the database, we are prefixing it with {noop}. This prefix tells Spring Security to use NoOpPasswordEncoder for password verification while authenticating the application user.
Register InMemoryUserDetailsManager Bean
Now with the list of UserDetails objects in hand, we can create and register an InMemoryUserDetailsManager bean inside a new @Configuration class like below.
@Configuration
public class SecurityBean {
@Autowired
private DbUserDetailsService dbUserDetailsService;
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
dbUserDetailsService.getAllUserDetails()
);
}
}
Verify authentication using AppUser
When we restart the application, we can notice Spring Security is no longer generating the random password. And it no longer creates the default user also. It uses InMemoryUserDetailsManager bean to hold the list of UserDetails mapped from the AppUser records in the database.
We can use any of these AppUser from the database to authenticate in order to access the secured REST APIs. Let's use Bob and his credential to access the ListStudents API in Postman using Basic Auth.