Spring Security – Set Password Strength and Rules

In modern web applications, securing user credentials plays a major role. Spring Security can provide a comprehensive framework to secure Spring-based applications. One of the crucial aspects of security is enforcing password strength and rules to prevent weak passwords which can be easily compromised. This article will guide you through the process of setting the password strength and rules in the Spring Security project.

Enforcing the password strength and rules in Spring Security involves creating custom validators to ensure passwords meet the specific criteria. It can enhance security by preventing weak passwords that can easily compromised. The process can typically include defining the set of rules for what constitutes a strong password, creating the custom validator to enforce these rules, and integrating this validator into the application user registration or password change processes.

Key Terminologies:

  • Spring Security: The powerful and highly customizable authentication and access control framework for Java-based Spring framework. It is the de-facto standard for securing Spring-based applications.
  • Password Strength: The measure of how to secure the password is against the guessing or brute-force attacks. Strong passwords can typically include the mix of the upper and lower case letters, digits and special characters and are of the minimum length.
  • Validator: The Component in Spring that checks whether the given input meets certain criteria. Validators can be used to enforce the constraints on the data such as ensuring that a password meets the specific strength requirements.
  • ConstraintValidator: An interface in the Spring that can provides the logic to validate the specific constraint. Implementation of this interface perform the actual validation logic of the application.
  • Constraint: In the context of validation, the constraint is the rule that the data must satisfy. Example can include the @NotNull, @Size and @Pattern.
  • Custom Annotation: Annotation in java are metadata that can provides data about the program but are not part of the program itself. Custom annotations are user-defined annotations used to the create the specific rules or behaviors such as validating the password field.

Implementation to Set Password Strength and Rules in Spring Security

Below are the implementation steps to Set Password Strength and Rules in Spring Security.

Step 1: Create a new Spring Boot project using Spring Initializr and add the required dependencies as shown below:

  • Spring Web
  • Spring Security
  • Lombok
  • Spring DevTools
  • Thymeleaf

After the project creation done, the folder structure will be like the below image:.


Step 2: Open the application.properties file and add the configuration for the security username and password, database, thymeleaf of the Spring Security application in the project.

spring.application.name=spring-security-password-strength

# Server configuration
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/example
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

# Thymeleaf settings
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html

# Logging configuration
logging.level.org.springframework=INFO
logging.level.com.example.security=DEBUG

spring.main.allow-circular-references=true


Step 3: Create new package named model and in that package, create the new Java class named User.

Go to src > org.example.springsecuritypasswordstrength > model > User and put the below code.

Java
package org.example.springsecuritypasswordstrength.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

/**
 * Represents a user entity in the application.
 */
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;

    // Getters and Setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}


Step 4: Create new package and it named as the repository in that package create the new Java class and it named as UserRepository.

Go to src > org.example.springsecuritypasswordstrength > repository > UserRepository and put the below code.

Java
package org.example.springsecuritypasswordstrength.repository;

import org.example.springsecuritypasswordstrength.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}


Step 5: Create new package and it named as the service in that package create the new Java class and it named as UserService.

Go to src > org.example.springsecuritypasswordstrength > service > UserService and put the below code.

Java
package org.example.springsecuritypasswordstrength.service;



import org.example.springsecuritypasswordstrength.model.User;
import org.example.springsecuritypasswordstrength.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;





@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void registerUser(String username, String rawPassword) {
        String encodedPassword = passwordEncoder.encode(rawPassword);
        User user = new User();
        user.setUsername(username);
        user.setPassword(encodedPassword);
        userRepository.save(user);
    }

    public User findUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
}


Step 6: Create the new package and it named as the config in that package create the new Java class and it named as SecurityConfig.

Go to src > org.example.springsecuritypasswordstrength > config > SecurityConfig and put the below code.

Java
package org.example.springsecuritypasswordstrength.config;


import org.example.springsecuritypasswordstrength.model.User;
import org.example.springsecuritypasswordstrength.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.SecurityFilterChain;




@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private UserService userService;

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

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            User user = userService.findUserByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException("User not found");
            }
            return org.springframework.security.core.userdetails.User.builder()
                    .username(user.getUsername())
                    .password(user.getPassword())
                    .roles("USER")
                    .build();
        };
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests
                                .requestMatchers("/register").permitAll()
                                .anyRequest().authenticated()
                )
                .formLogin(formLogin ->
                        formLogin
                                .loginPage("/login")
                                .permitAll()
                )
                .logout(logout ->
                        logout
                                .permitAll()
                );
        return http.build();
    }
}


Step 7: Create a new package named config and in that package, create the new Java class and it named as CustomPasswordEncoder.

Go to src > org.example.springsecuritypasswordstrength > config > CustomPasswordEncoder and put the below code.

Java
package org.example.springsecuritypasswordstrength.config;



import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Set;




public class CustomPasswordEncoder implements PasswordEncoder {

    private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    @Override
    public String encode(CharSequence rawPassword) {
        validatePassword(rawPassword.toString());
        return bCryptPasswordEncoder.encode(rawPassword);
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return bCryptPasswordEncoder.matches(rawPassword, encodedPassword);
    }

    private void validatePassword(String password) {
        PasswordValidator passwordValidator = new PasswordValidator(password);
        Set<ConstraintViolation<PasswordValidator>> violations = validator.validate(passwordValidator);
        if (!violations.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (ConstraintViolation<PasswordValidator> violation : violations) {
                sb.append(violation.getMessage()).append(" ");
            }
            throw new ConstraintViolationException(sb.toString().trim(), violations);
        }
    }

    public static class PasswordValidator {
        @NotNull
        @Size(min = 8, message = "Password must be at least 8 characters long")
        private final String password;

        public PasswordValidator(String password) {
            this.password = password;
        }
    }
}


Step 8: Create a new package named controller and in that package, create the new Java class named WebController.

Go to src > org.example.springsecuritypasswordstrength > controller > WebController and put the below code.

Java
package org.example.springsecuritypasswordstrength.controller;


import jakarta.validation.ConstraintViolationException;
import org.example.springsecuritypasswordstrength.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;


@Controller
public class WebController {

    @Autowired
    private UserService userService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @GetMapping("/register")
    public String showRegistrationForm() {
        return "register";
    }

    @PostMapping("/register")
    public String registerUser(
            @RequestParam("username") String username,
            @RequestParam("password") String password,
            @RequestParam("confirmPassword") String confirmPassword,
            Model model) {

        if (!password.equals(confirmPassword)) {
            model.addAttribute("error", "Passwords do not match");
            return "register";
        }

        try {
            userService.registerUser(username, password);
        } catch (ConstraintViolationException e) {
            model.addAttribute("error", e.getMessage());
            return "register";
        }

        return "redirect:/login";
    }

    @GetMapping("/login")
    public String showLoginForm() {
        return "login";
    }
}


Step 9: Open the main class and write the below code. (No change are required)

Java
package org.example.springsecuritypasswordstrength;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringSecurityPasswordStrengthApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityPasswordStrengthApplication.class, args);
    }

}


Step 10: Create the Register HTML page.

Go to src > main > resources > templates > register.html and put the below code.

HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Register</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .container {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            width: 300px;
        }
        h1 {
            text-align: center;
            color: #333;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
            color: #555;
        }
        .form-group input {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        .form-group input:focus {
            border-color: #007bff;
        }
        .btn {
            width: 100%;
            padding: 10px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        .btn:hover {
            background-color: #0056b3;
        }
        .error {
            color: red;
            text-align: center;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<div class="container">
    <h1>Register</h1>
    <form th:action="@{/register}" method="post">
        <div class="form-group">
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required />
        </div>
        <div class="form-group">
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required />
        </div>
        <div class="form-group">
            <label for="confirmPassword">Confirm Password:</label>
            <input type="password" id="confirmPassword" name="confirmPassword" required />
        </div>
        <button type="submit" class="btn">Register</button>
        <div th:if="${error}" class="error">
            <p th:text="${error}"></p>
        </div>
    </form>
</div>
</body>
</html>


Step 11: After completing the project, we will run this application and it will run on port 8080.


Register page

API:

http://localhost:8080/register

Output:

The above image shows the validation error message when the user attempts to register with the invalid password. Make sure the passwords meets all the criteria set in the PasswordConstraintValidator.

By following these steps, we can effectively enforce the password strength and rules in the Spring Security application and it can enhancing the security of the user credentials.



Contact Us