UserDetailsService and UserDetailsManager: Handling Users in Spring Security

Table of Contents

In our previous article, we discussed how to configure Spring Security to protect our Spring MVC application. Once security is in place, accessing a protected resource redirects us to a login page. But how do we actually log in? We need a username and password, but where do these credentials come from? In this article, we’ll focus on UserDetailsService/UserDetailsManager and their role in creating and storing users in Spring Security.

Understanding UserDetails in Spring Security

Instead of manually defining a data structure to store application user information, the Spring team provides the UserDetails interface. This interface represents the essential user-related details required for authentication and authorization.

Rather than implementing UserDetails from scratch, Spring Security offers a built-in implementation called User, which already includes critical user attributes such as:

  • Username
  • Password
  • Granted authorities (roles and permissions)

The UserDetails Interface


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

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

The User Class:

public class User implements UserDetails, CredentialsContainer {

    private static final long serialVersionUID = 620L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;
    
    // Constructors
    // getters & setters 
    // ......
}

The User class follows the Builder design pattern, making it easy to create user instances:

 UserDetails user = User.withUsername("admin")
                .password("admin123")
                .roles("ADMIN")
                .build();

The User class simplifies user creation by allowing us to define a username, an encoded password, and assign roles or authorities in a fluent and readable way.

User Management With UserDetailsManager

Spring Security provides various implementations for managing users. The UserDetailsManager interface provides several methods to manage users, such as:

public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);
    void updateUser(UserDetails user);
    void deleteUser(String username);
    void changePassword(String oldPassword, String newPassword);
    boolean userExists(String username);
}

Spring Security offers different UserDetailsManager implementations to store user data in various ways:

In-Memory Storage (InMemoryUserDetailsManager)

This implementation stores user details in memory using a HashMap. It is useful for prototyping and testing but is not suitable for production since data is lost when the server restarts.

Example:

Add this configuration class to your project (you can find the starter source code on GitHub), restart your application, and try logging in using the username and password used.

@Configuration
@EnableWebSecurity()
public class SecurityConfig {

    @Bean
    public InMemoryUserDetailsManager userDetailsService() throws Exception {


        UserDetails user = User.withUsername("admin")
                .password("admin123")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user);

    }
}

This code snippet creates a UserDetailsManager with one user: (username “admin”, password “admin123”, and role “ADMIN”)

Obviously, you will encounter the following error:

UserDetailsService needs a passwordEncoder

The error indicates that Spring Security cannot find a PasswordEncoder. Spring Security requires a PasswordEncoder to ensure that passwords are stored securely.

Configuring Password Encoding with UserDetailsService

  1. BCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
  1. NoOpPasswordEncoder (Not Secure – For Testing Only)
@Bean
public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}
  1. Pbkdf2PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
    return new Pbkdf2PasswordEncoder();
}
  1. SCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
    return new SCryptPasswordEncoder();
}

Since we passed the password directly without encrypting it when calling the password() method while creating our user, let's inform Spring Security that no encoding is being used (by simply using NoOpPasswordEncoder).
 
Below is the complete version of the SecurityConfig class:

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {




    @Bean
    public UserDetailsService userDetailsService() throws Exception {


        UserDetails user = User.withUsername("admin")
                .password("admin123")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user);

    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return  NoOpPasswordEncoder.getInstance();
    }

}

Note: Spring provides alternative approaches for user storage and retrieval beyond InMemoryUserDetailsManager, such as JdbcUserDetailsManager, which we will explore in future articles. Alternatively, you can implement your custom UserDetailsService to retrieve users from any source you prefer.

You might be wondering about the appropriate scenarios to use UserDetailsService versus UserDetailsManager. This will be the subject of our next section.

UserDetailsService VS. UserDetailsManager

UserDetailsManager is an extension of UserDetailsService that provides CRUD operations for managing users. Unlike UserDetailsService, which only retrieves user details, UserDetailsManager also supports:

  • Creating new users
  • Updating user details
  • Deleting users
  • Changing passwords

Since UserDetailsManager extends UserDetailsService, it inherits the loadUserByUsername() method but also provides user management capabilities.

🔹Use UserDetailsService when your system only authenticates users without modifying them.
🔹 Use UserDetailsManager when you need dynamic user management (e.g., allowing users to sign up, update their profile, or reset their password).

Conclusion

Spring Security provides a flexible and powerful authentication system. By using UserDetails and UserDetailsManager, we can create users and store them in memory, databases, or custom repositories. To enhance security, passwords should always be encoded using a secure hashing algorithm like BCrypt. With these foundational concepts, you can build robust authentication mechanisms in your Spring applications.

Leave a Reply

Your email address will not be published. Required fields are marked *

Join the Tribe