In this article, we will learn about Spring security UserDetailsService. We will learn how to create a custom database-backed UserDetailsService for authentication with Spring Security.
Introduction
If we are using Spring security in our application for the authentication and authorization, you might know UserDetailsService
interface. The UserDetailsService is a core interface in Spring Security framework, which is used to retrieve the user’s authentication and authorization information.
This interface has only one method named loadUserByUsername()
which we can implement to feed the customer information to the Spring security API. The DaoAuthenticationProvider
will use this information to load the user information during authentication process. Here is the definition of the interface.
UserDetails loadUserByUsername(java.lang.String username) throws UsernameNotFoundException
Let’s inspect some important aspects of the interface and its method
- Spring security does not use this method directly at any place.
- It will only store user information encapsulated into Authentication objects.
Let’s check how to define a custom Spring security UserDetailsService for our application.
1. Application Setup
Let’s start by creating the web application. We can use the IDE or Spring Initializr to bootstrap our application. We are using Spring Initializr for this post as it offer a fast way to pull the dependencies to build our application.
- Go to https://start.spring.io/.
- Select the web, Spring security, Thymeleaf and MySQL as dependencies.
- Fill information for the group and artifact and click on the “Generate” button.
If you like to use the <a title="Spring Boot" href="https://javadevjournal.com/spring-boot/" target="_blank" rel="noopener noreferrer">Spring Boot</a> CLI
to generate the project structure, run the following command from the terminal.
spring init --name spring-security-custom-userdetailservice --dependencies=web,thymeleaf,security spring-security-success-handler
Using service at https://start.spring.io
Project extracted to '/Users/pooja/spring-security-success-handler'
Here is our pom.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<groupId>com.javadevjournal</groupId>
<artifactId>spring-security-success-handler</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-success-handler</name>
<description>How to redirect user to different page on login success</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
We are adding MySQL as the dependencies to store user login details in the database and will customize the user service to get user login details from the DB.
2. Database Configuration
We are using Spring JPA for our application, which will handle most of the complex Database work (e.g. defining the JDBC connection and queries, etc,). Spring JPA need database information to store and retrieve information. We can provide this information using application.properties
file. Let’s provide the connection information:
spring.jpa.generate-ddl=true
spring.datasource.url=jdbc:mysql://localhost:3306/spring-security-user-detail-service?useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
When we will start our application, Spring JPA will automatically create the required table structure for us.
3. Customer Model
We need a customer entity to store the user details in the database. I am keeping the customer model simple for this article, but the real world customer entity can be complex.
@Entity
public class CustomerModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private String password;
public Customer() {}
//getter & setters methods
}
4. Returning Customer Information
For the Spring security, we need to load customer information from the database using username (email in our case). To achieve this, we will use the Spring data repository feature by extending JpaRepository
interface. Let’s create a customer repository:
import com.javadevjournal.jpa.entities.CustomerEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository extends JpaRepository < CustomerEntity, Long > {
CustomerEntity findByEmail(String email);
}
I am creating a method findByEmail
since email id is also the username in my case, but you can name the method as findByUsername
.
5. The UserDetailsService
This is the main configuration for our application. To load the customer details, we need to implement UserDetailsService
interface. It uses spring Security UserDetailsService
interface in order to lookup the username, password and GrantedAuthorities
for any user.
import com.javadevjournal.jpa.entities.CustomerEntity;
import com.javadevjournal.jpa.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private CustomerRepository customerRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final CustomerEntity customer = customerRepository.findByEmail(username);
if (customer == null) {
throw new UsernameNotFoundException(username);
}
UserDetails user = User.withUsername(customer.getEmail()).password(customer.getPassword()).authorities("USER").build();
return user;
}
}
This is the minimal code to show you how to create a custom UserDetailsService in Spring security. Let’s discuss some important points.
- We provided implementation for the
UserDetailsService
andloadUserByUsername
method. - We are using the
CustomerRepository
to find the user by username. - This service will return the user details in the UserDetails object (we can also create a wrapper for this).
Please keep in mind following important points while creating this service for your application.
- We are using a hard-coded authority for the user, but for production application, we should load the permissions / authorities from the database.
The core idea is to return the User
instance with populated values. You are free to implements UserDetails
interface as per your requirement.
6. Spring Security Configuration
The last step is to configure Spring security to use our custom Spring security UserDetailsService to load user information during authentication. This is how our Spring security config class look like:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
}
- The
@EnableWebSecurity
enable Spring Security’s web security support and provide the Spring MVC integration. - We are extending
WebSecurityConfigurerAdapter
class, which provides a convenient base class for creating aWebSecurityConfigurer
instance. - We are injecting custom UserDetailsService in the
DaoAuthenticationProvider
. Spring security use this provider to load the customer information.
7. Application Testing
Let’s star tour application to see the custom provider in action.Once the application is up and running, open the following URL http://localhost:8080/login in a browser window. A login box will appear like below:
If you provide a correct username and password, it will allow you to login to the system. For invalid username or password, system will throw error back on the login screen:
Summary
In this post, we learn about Spring security UserDetailsService. We saw how to inject custom UserDetailsService to load customer information during authentication.
Hello I am writing a little project and i have problem with login, after register user is right add to database, but after i’m trying login with users email and password I can’t do it
here is my code:
loginform.html
It looks like you are using your own login controller? Can you show me the login controller? Else change the
@{/loginform}
to@{/login}
to let spring security handle login for you using your custom user details service.Also missing the Authentication Provider configuration.Take a look at https://javadevjournal.com/spring-security/spring-security-login/
Finally! A well explained post about this, thank you very much for this my friend! Gonna keep checking your blog.
Happy that it was useful for you Edd!!