Password encoding in spring security

In this article we will look at the different options for password encoding in Spring Security. We are using Spring Boot for our sample application and this article hold good if you are looking on how to perform password encoding in Spring Boot. We will not cover the outdated password encoding techniques in Spring Security.

1. Introduction

Spring Security provides password encoding feature using the PasswordEncoder interface. It’s a one way transformation, means you can only encode the password, but there is no way to decode the password back to the plaintext form. The standard use of this feature is to compare the user-provided password at the time of authentication (encoding the user password). In this article we are using the BCryptPasswordEncoder implementation for the password encoding in spring security.

2. Setting up Application

Before we start, let’s create a simple Spring Boot application with Spring security and spring web starter as dependencies. We can use our IDE to generate the application or if you prefer, you can use Spring initializer to bootstrap your application.

Password Encoding with Spring Security

Click  “Generate Button” to download the project structure your local machine. We are adding the following dependencies in our project.

  • Web Module – For web application setup.
  • Spring Data JPA – We will store the user detail and password in DB.
  • Spring Security

We will also add Thymeleaf dependency in the application to build the HTML pages. This is how the pom.xml look like:

<?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>password-encoding-in-spring-security</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>password-encoding-in-spring-security</name>
   <description>Demo project for Spring Boot</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>
      </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>

3. Spring Security Password Encoder

Spring Boot provides different password encoding implementation with the flexibility to choose a certain encoder based on our need. Spring Security 5.0 introduces DelegatingPasswordEncoder as the new encoder to address following issues:

  1. Encode password using latest storage recommendations.
  2. Allowing for upgrading the encoding.

Let’s look at some recommended password encoder in Spring security for encoding the password.

4. Password Encoding using BCryptPasswordEncoder

The BCryptPasswordEncoder implementation uses the bcrypt algorithm to hash the passwords. Define the BCryptPasswordEncoder as a bean in our configuration:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class AppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Let’s discuss some important points about this password encoder

  1. The BCryptPasswordEncoder provides strength as a constructor argument to compute the hash. The larger value shows the longer time to hash but stronger password.
  2. We can also pass SecureRandom to randomize the generated hashes.
  3. This algorithm generate String of length 60, keep that in mind while you are designing the database tables.

Here is the updated version of the password encoder if you don’t want to use default configurations:

@Bean
public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder(20);
}

Here is a sample output(default encoder) for the encoder:

$2a$10$96AvWDbaCiJlfzZ1fwZej.GSXFkAmPjqj12uSMa/RyB6zsIvsgNy.

4.1 How BCrypt Works

Before we move to the next section, Let’s answer one important question about the BCrypt algorithm – “If salt is random when encoding is called, how can BCrypt check if password submitted by user matches with the store one?“. 

  1. BCrypt generates the salt internally (we need not pass it).
  2. It stores contacts with the cost and the cipher in a single field delimited by $.
  3. When the user tries to login, it uses it on authentication. 

To understand it better, let’s look at the encoded password:

$2a$10$96AvWDbaCiJlfzZ1fwZej.GSXFkAmPjqj12uSMa/RyB6zsIvsgNy.

  1. The first part represents the version ($2a, $2b, $2y) – in our case it is $2a$.
  2. The second part is the strength – $10$ for our example.
  3. The next 22 characters are the salt for the password (96AvWDbaCiJlfzZ1fwZej.).
  4. Remaining section is the hashed password.

Here is the breakdown for better clarity:

BCrypt detail

5. Setting up Application

Let’s setup our application. We will perform the following steps for the setup:

  1. Customer entity to store customer information.
  2. Create and configure DB.
  3. Registration page for the user registration.

5.1. Customer Entity.

To store the data in the database, Create a CustomerEntity. This is how the CustomerEntity class look like:

@Entity
public class CustomerEntity{

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;
 private String firstName;
 private String lastName;
 private String email;
 private String password;

 public Customer() {
 }
 //get & set methods
}

We also need to configure the database connection details. Setup the details in the application.properties file.

spring.jpa.generate-ddl=true
spring.datasource.url=jdbc:mysql://localhost:3306/spring-security-password-encoding?useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.jpa.generate-ddl property helps us to generate the DDL in the startup. Here is the database view of our customer table:

password encoding in spring security table

If you like to run the DDL yourself, here is the sample DDL SQL script for your reference:

CREATE TABLE `customer_entity` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `first_name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `last_name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `password` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

Our basis setup is complete, To test our application, we will perform these 2 additional steps to make sure our password encoding with spring security works as expected:

  1. Create customer registration page.
  2. Login page to validate customer credentials (given during registration process)

6.  Encode Password on Registration

To encode the password during registration, we will use the PasswordEncoderin our CustomerService to hash it. Here is how we will do this:

package com.javadevjournal.service;

import com.javadevjournal.data.CustomerDto;
import com.javadevjournal.entity.CustomerEntity;
import com.javadevjournal.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service("customerService")
public class CustomerService {

    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    PasswordEncoder passwordEncoder;

    public CustomerEntity saveCustomer(final CustomerDto customerData) {
        CustomerEntity customerModel = populateCustomerData(customerData);
        return customerRepository.save(customerModel);
    }

    private CustomerEntity populateCustomerData(final CustomerDto customerData) {
        CustomerEntity customer = new CustomerEntity();
        customer.setFirstName(customerData.getFirstName());
        customer.setLastName(customerData.getLastName());
        customer.setEmail(customerData.getEmail());
        customer.setPassword(passwordEncoder.encode(customerData.getPassword()));
        return customer;
    }
}

To complete it, let’s take a quick look at the CustomerRepositoty class.Please read Spring JPA for more details on sophisticated support to build repositories based on Spring and JPA.

package com.javadevjournal.repository;

import com.javadevjournal.entity.CustomerEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends JpaRepository < CustomerEntity, Long > {
    
}

7.  Encode Password on Registration

Above code will encode the password before storing it in the database, Let’s see the other part of the password encoding in Spring Security. We like to use the same password encoding mechanism during the login process. Here are the steps to accomplish this:

  1. Inject password provider in the authentication provider.
  2. Inject custom UserDetailService class.
  3. Use the custom authentication provider

Our final security configuration class:

package com.javadevjournal.security;

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(name = "customUserService")
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").permitAll().and().formLogin().defaultSuccessUrl("/welcome", true);
    }


    @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());
    }
}

8. Testing Password Encoding With Spring

Let’s run our application to see the password encoding in action.Once the application is running open https://localhost:8080/register (Please refer to the source code for the controller code). The URL will display the register page:

Customer Registration Page

Fill all details and click on the “Register” button system will encode the password before creating a record in the database. If we check the database, we can see it encodes the password.

database

To test if our application is working with password encoding during login, open the https://localhost:8080/login URL and try with both correct and wrong credentials. For the correct one, you will be taken to the welcome page but for the wrong credentials, Spring Security will show an error message.

login page spring security

Welcome page on successful login

Welcome Page

Error on wrong credentials:

Wrong Credential

9. Other PasswordEncoder

Let’s look at some other available PasswordEncoders:

9.1. Pbkdf2PasswordEncoder

The Pbkdf2PasswordEncoder implementation uses the PBKDF2 algorithm to hash the passwords. Here are some optional arguments for this encoder:

  • Hash Width – Size of the hash.
  • Iteration – Number of iterations for the password encoding.
  • Secret – Secret for the encoding process.

There are few important characteristics of this algorithm and we should keep in mind while using it.

  1. PBKDF2 is a deliberately slow algorithm.
  2. The algorithm is slow to make it password encoding difficult.

To use the Pbkdf2PasswordEncoder encoding in Spring Security application define the Pbkdf2PasswordEncoder bean:

@Bean
public PasswordEncoder passwordEncoder() {
    return new Pbkdf2PasswordEncoder();
}
//if you like to pass the custom parameters
@Bean
public PasswordEncoder passwordEncoder() {
    return new Pbkdf2PasswordEncoder("mysecret", 1000, 128);
}

Here is a sample output from the  Pbkdf2PasswordEncoder encoder:

Pbkdf2PasswordEncoder without custom parameters- f66dba2bb7391e6d3aca34a5dcf01f22e6293723b7d0390afc9c5ac8a06772d05e78f31a5fcdefed
Pbkdf2PasswordEncoder  with custom parameters 128 -  c69004d61a48b607c45a6090eb29efbb822c2c4b810ba0b7
Pbkdf2PasswordEncoder  with custom parameters 256 - fcd39e7bd05920c5ed2a73401ac58c4e1e9047a8244bdb7e5e853b8d714205b9bbe1b978a793ec66

9.2. SCryptPasswordEncoder

The SCryptPasswordEncoder implementation uses scrypt algorithm to hash the passwords.This algorithm is also a deliberately slow algorithm handle password cracking and requires large amounts of memory.

@Bean
public PasswordEncoder passwordEncoder() {
    return new SCryptPasswordEncoder();
}

We have the option to pass certain parameters to customize the output of the SCryptPasswordEncoder

  • cpuCost – Cpu cost of the algorithm. It’s an int value and must be the power of 2.
  • memoryCost – Memory cost of the algorithm (by default 8).
  • parallelization – The parallelization of the algorithm (not used by algorithm).
  • keylength – Key length of the algorithms.Default set to 32.
  • saltLength – Salt length. Default value is 64.

 

9.3. Argon2PasswordEncoder

The Argon2PasswordEncoder implementation uses the Argon2 algorithm to hash the passwords. This algorithm is very much similar to the other algorithms described in this section except that the current implementation of Argon2PasswordEncoder requires BouncyCastle.

@Bean
public PasswordEncoder passwordEncoder() {
    return new Argon2PasswordEncoder();
}

Summary

In this article, we saw the different option for password encoding in Spring Security. We took a deeper dive to encode the password with Spring security using the BCryptPasswordEncoder. In the later section, we saw the other encoder available in the Spring security for password hashing. The source code for this article is available on the GitHub.

2 thoughts on “Password encoding in spring security”

Comments are closed.