Custom Password Encoder in Spring Security

In this tutorial, you will learn how to create a custom password encoder in a Spring Boot application that uses Spring Security.

Table of contents

  1. Create a Spring Boot project and add database connection properties.
  2. Add a User model.
  3. Create a User repository.
  4. Implement a custom PasswordEncoder.
  5. Create a service class that implements UserDetailService
  6. Add a config class that extends WebSecurityConfigurerAdapter .
  7. Create a User controller class.
  8. Testing the application.

Create a Spring Boot project and add database connection properties

Go to Spring Initializr  and create a Spring Boot project with Spring Web, Spring Security, Spring Data JPA, and MySQL Driver maven dependencies.

Connection Properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/user_database
spring.datasource.username=username
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true

Add a User Model

User class creates an instance of user details.

import javax.persistence.*;

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private int id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    //Generate Getters, Setters, construc and toString methods
}

Create a User Repository

UserRepository class to saves and retrieves a user object from the database.

@Repository
public interface UserRepository extends JpaRepository<User,Integer> {
}

Implement a Custom PasswordEncorder

Below is an example of a class that implements a PasswordEncoder interface. We will use this class to implement our own, custom password encoder. A PasswordEncoder is an interface in Spring Security that we can use to make our class provide an implementation of our own password encoder.

Implementations of the password encoder include BcryptPasswordEncoder, NoOpPasswordEncoder and StandardPasswordEncoder.

The default and custom implementations must override encode and matches methods in password encoder.

The encoding method encodes the raw password. Generally, a good encoding algorithm applies a SHA-1  or greater hash combined with an 8-byte or greater randomly generated salt.

The matches method verifies that the encoded password obtained from storage matches the submitted raw password after it has been encoded. Return true if the password match, false if they do not. The stored password itself does not get decoded.

To use this password encoder, create a bean of custom password and inject it to the user service where the user password is encoded before saving it to the database.

public class CustomPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence plainTextPassword) {
        return BCrypt.hashpw(plainTextPassword.toString(),BCrypt.gensalt(8));
    }

    @Override
    public boolean matches(CharSequence plainTextPassword, String passwordInDatabase) {
        return BCrypt.checkpw(plainTextPassword.toString(),passwordInDatabase);
    }
}

Create a service class that implements UserDetailService

UserDetailService provides a method to find a user by username.

The method finds a user during authentication but since our interest is in password encoding, we are going to return null user details and authorities.

Register and Find Methods

The register method receives a user object from the controller encodes the plain text password and then persists the user in the database.

The find method returns the user object to the controller which can then be viewed from the client using postman.

@Service
public class UserService implements UserDetailsService {

    private UserRepository userRepository;
    private CustomPasswordEncoder customPasswordEncoder;

    @Autowired
    public UserService(UserRepository userRepository,
                       @Lazy CustomPasswordEncoder customPasswordEncoder){
        this.userRepository = userRepository;
        this.customPasswordEncoder = customPasswordEncoder;

    }

    public void registerUser(User user) {
        User newUser = new User();
        newUser.setId(user.getId());
        newUser.setFirstName(user.getFirstName());
        newUser.setLastName(user.getLastName());
        newUser.setEmail(user.getEmail());
        newUser.setUsername(user.getUsername());
        newUser.setPassword(customPasswordEncoder.encode(user.getPassword()));
        userRepository.save(newUser);

    }

    public User findUserById(int id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new NullPointerException("user not found"));
    }

  @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = new User();
            return new org.springframework.security.core.userdetails.User(user.getUsername(),
                    user.getPassword(),Collections.emptyList());

    }
}

Create a Class that Extends WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter provides a convenient base class for creating a WebSecurityConfigurerAdapter instance.

Dao authentication provider retrieves user details from a user detail service.

Authentication manager builder adds authentication providers.

HTTP security allows configuring web-based security for specific HTTP requests.

Creating a password encoder bean ensures that we can inject it into the user service class to encode the user password.

@Configuration
@EnableWebSecurity
public class CustomPasswordEncoderConfig extends WebSecurityConfigurerAdapter {

    private UserService userService;


    @Autowired
    public CustomPasswordEncoderConfig(UserService userService){
        this.userService = userService;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**")
                .permitAll();
    }


    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(customPasswordEncoder());
        daoAuthenticationProvider.setUserDetailsService(userService);
        return daoAuthenticationProvider;
    }

    @Bean
    public CustomPasswordEncoder customPasswordEncoder(){
        return new CustomPasswordEncoder();
    }
}

Create a User Controller Class

/user/register – create a new user object.

/user/find/{id} – retrieve a user instance.

@RestController
@RequestMapping("/user")
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }

    @PostMapping("/register")
    public void registerUser(@RequestBody User user){
        userService.registerUser(user);
    }

    @GetMapping("/find/{id}")
    public User findUserById(@PathVariable("id") int id){
        return userService.findUserById(id);
    }
}

Testing the Application

Create a new user object using postman using the following link.

http://localhost:8080/user/register

Retrieve the user object and note the password is now encoded.

http://localhost:8080/user/find/1

Conclusion

Password encoding is a security measure that ensures data integrity and confidentiality is safe from attackers. Security should also safeguard from other forms of vulnerabilities such as cross-site scripting, SQL injection, denial of service, and cross-site request forgery attacks. To ensure the safety of our application we need to incorporate a secure authentication and authorization mechanism and also ensure hashing of passwords.


Leave a Reply

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

Free Video Lessons

Enter your email and stay on top of things,

Subscribe!