Spring Security UserDetailsService

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

  1. Spring security does not use this method directly at any place.
  2. 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.

spring-security-success-handler

If you like to use the Spring Boot 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 GrantedAuthoritiesfor 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.

  1. We provided implementation for the UserDetailsService and loadUserByUsername method.
  2. We are using the CustomerRepository to find the user by username.
  3. 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.

  1. 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());
    }
}
  1. The @EnableWebSecurity enable Spring Security’s web security support and provide the Spring MVC integration.
  2. We are extending WebSecurityConfigurerAdapter class, which provides a convenient base class for creating a WebSecurityConfigurer instance.
  3. 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:

custom success handler

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:

Wrong Credential

Summary

In this post, we learn about Spring security UserDetailsService. We saw how to inject custom UserDetailsService to load customer information during authentication.

0 0 vote
Article Rating

Manish Sharma

Manish's primary interests are Java, Spring Boot and Spring. His focus is more toward the automations and testing.Manish love travelling and when not working, he might be exploring some new destination.

Subscribe
Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x