Spring Security - Custome UserDetailsService

We learn about JDBC Authentication, but in real world, it is more common to customize UserDetailsService. This allows more flexibility When it comes to authenticating user.

UserDetails Interface

We can understand UserDetails class first before looking at UserDetailsService interface.

UserDetails Interface provides essential user info. This class will then be encapsulated into Authentication object. Authentication object contains a Principal. The Principal is often a UserDetails.

User is the reference implementation of UserDetails interface.

Here is the source code for UserDetails

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.springframework.security.core.userdetails;

public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();
}

UserDetailsService Interface

Core interface which loads user-specific data.

It is used throughout the framework as a user DAO and is the strategy used by the DaoAuthenticationProvider.

1
2
3
4
5
package org.springframework.security.core.userdetails;

public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

loadUserByUsername(String) method locates the user based on the username. if the user could not be found or the user has no GrantedAuthority, a UsernameNotFoundException will be thrown

Spring Security already contains implementations for UserDetailsService.

  • CachingUserDetailsService
  • InMemoryUserDetailsManager
  • JdbcDaoImpl
  • JdbcUserDetailsManager
  • LdapUserDetailsManager
  • LdapUserDetailsService

If you need more flexibility to get UserDetails from the username, you can provide your own implementation.

UserDetailsService Simple Example

Here UserDetailsService.loadUserByUsername method returns User, which is the reference implementation. If you need more customization, you can implement UserDetails interface for your user entity. That way you can directly return user entity instead of creating User object.

SimpleUserDetailsServiceImpl.java - a very simple UserDetailsService implementation that returns a user with password equals ‘password’. The returned user’s password should be encoded.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.security.core.userdetails.User;

@Service
public class SimpleUserDetailsServiceImpl implements UserDetailsService {
@Autowired
PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username.equals("user")) {
return new User(username, passwordEncoder.encode("password"),
Collections.singletonList(new SimpleGrantedAuthority("User")));
}
throw new UsernameNotFoundException("Error logging in " + username);
}
}

SecurityConfiguration.java - To use the above custom UserDetailsService in security configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().formLogin();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}

userDetailsService(UserDetailsService) method adds authentication based upon the custom UserDetailsService that is passed in.

UserDetailsService that works with JPA

This is a more realistic code example. UserDetailsService implementation that loads user from a database.

UserDetailsServiceImpl.java - UserDetailsService implementation that loads user from a database repository.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.security.core.userdetails.User;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;

@Transactional
public MyUser findByUsername(String username){
return userRepository.findByUsername(username).orElse(null);
}

@Transactional
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
MyUser user = userRepository.findByUsername(s).orElseThrow(() ->new UsernameNotFoundException("Uesr not found."));
var authorities = user.getRoles().stream().map(r -> new SimpleGrantedAuthority(r.getName())).collect(Collectors.toList());
return new User(user.getUsername(), user.getPassword(), authorities);
}
}

Reference