User management in an application defines how a user is created/updated, the characteristics of a user(credentials, email address, a flag to indicate account status), and the authentication process. Also, if we are using spring security in our application, It is important to understand the basics of spring security user management.
In this article, we will learn how spring security handles user management. We will also learn the main components of Spring security user management.
We will also create a small example application and customize a few of the components of the user management.
Table of Contents
Components of user management
The below image shows the main components of Spring security user management.
The main components and their role are:
- UserDetailsManager: This interface extends UserDetailsService interface. It is also responsible for user create, updates, or delete operations.
- UserDetailsService: This interface defines a method that finds the user by username.
- UserDetails: This interface describes the user.
- GrantedAuthority: GrantedAuthority interface represents the authorities that a user can have.
Customizing the user details
In this example, we will learn how to customize the default user representation provided by the spring security.
We will also create a Spring boot application with spring security dependency. We define a custom User class to represent the user and also create a custom user details service to load the user from an in-memory list.
Create an application
Create a Spring boot application with the following dependencies.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
We have added the spring web starter dependency, and also the security starter dependencies to our applications pom.xml file.
Define a User details class
Create a custom java class with the name User that represents our application’s user.
Also, our custom user class needs to implement Spring security’s UserDetails interface.
Finally, the below code shows a simple implementation of the UserDetails contract.
public class User implements UserDetails { private static final long serialVersionUID = 1L; private final String username; private final String password; private final String authority; public User(String username, String password, String authority) { this.username = username; this.password = password; this.authority = authority; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(() -> authority); } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
The Spring securities UserDetails interface has the following methods:
- getAuthorities(): This method returns all the authorities a user has. The GrantedAuthority contract represents user authority.
- These authorities describe the privileges a user has(like read, write, update, etc.) or actions a user can perform. In our example, we also have a simple String field called authority that represents the user’s authority.
- In our example, we have returned true for the below methods, as it is a simple implementation. We can also add the required logic to customize the behavior.
- getPassword() & getUsername(): Returns the user credentials(password & username).
- isAccountNonExpired(): Return true if user account is not expired.
- isAccountNonLocked(): Return true if user account is not locked.
- isCredentialsNonExpired(): Return true if user credentials are not expired.
- isEnabled(): Return true if user is enabled.
Implement a custom UserDetailsService
To use the newly created custom user details class, we need to implement the UserDetailsService interface.
The UserDetailsService is responsible for loading the user by username from a database/LDAP or any other storage(From a local collection in our example).
Create a custom InMemoryUserDetailsService class that implements UserDetailsService interface.
public class InMemoryUserDetailsService implements UserDetailsService { private final List<UserDetails> users; public InMemoryUserDetailsService(List<UserDetails> users) { this.users = users; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return users.stream() .filter(u -> u.getUsername().equals(username)) .findFirst() .orElseThrow(() -> new UsernameNotFoundException("Username not found!!")); } }
We are maintaining users in a local collection called users.
The loadUserByUsername method searches for available user instances from the “users” collection and returns the user instance if the username matches.
If the username doesn’t match any of the existing users, it throws UsernameNotFoundException.
Define the security configuration
To wire the custom UserDetails and UserDetailsService implementation to spring security, we need to configure the UserDetailsService bean.
Also, create a spring configuration java class with the name AuthConfig, as shown below.
@Configuration public class AuthConfig { @Bean public UserDetailsService userDetailsService() { UserDetails u = new User("arun", "12345", "READ"); List<UserDetails> users = List.of(u); return new InMemoryUserDetailsService(users); } @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
We have created an instance of the User class, which is our customized UserDetails instance.
The user will have a username arun, password 12345, and an authority READ.
We have also created a list of users and passing it to the class InMemoryUserDetailsService‘s constructor that implementations the UserDetailsSerice interface.
The collection of users passed to the constructor adds the users into the locally defined in-memory users list of the InMemoryUserDetailsService class.
We also have to define a Password encoder bean if we are customizing the UserDetailsService.
Create a simple HTTP GET endpoint, as shown below.
The user will land on this path after successful authentication.
@RestController public class HelloRestController { @GetMapping("/") public String getMessage() { return "Hello!!"; } }
Testing the implementation
Start the Spring boot application. Also, access the application from a browser(at location http://localhost:8080).
Spring security displays the default login page, as shown below.
Enter the credentials of the user and click on the Sign-in button.
Finally, we get a hello message if the user gets authenticated successfully.
Conclusion
In this article, we learned how spring security represents the user details, user authorities, etc.
We also learned how spring security provides basic functionalities required to load, manage users, and their authentication.
We have also customized user details by creating a simple user implementation and custom user details service to use the custom user implementation.
Example code is available on GitHub.