Horje
Prevent Brute Force Authentication Attempts with Spring Security

Brute force attacks are a common threat where an attacker tries multiple combinations of usernames and passwords to gain authorized access to the system. To protect against such attacks, it is crucial to implement mechanisms that can detect and block repeated failed login attempts. Spring Security can provide robust support to help mitigate these threats. In this article, we will learn how to prevent brute-force authentication attempts using Spring Security.

The main idea is to track the number of failed login attempts and lock the user account temporarily after the predefined number of unsuccessful attempts. We will use the in-memory approach for simplicity but it can extended to use the database for better scalability of the application.

Implementation of Prevent Brute Force Authentication Attempts with Spring Security

Step 1: Create a Spring Project

Create a new Spring project using spring Initializr. On creating the project, add the following dependencies.

Dependencies:

  • Spring Web
  • Spring Security
  • Lombok
  • Spring DevTools
  • Spring Data JPA
  • MySQL Driver

After creating the project, the folder structure will be like below image:

Folder Structure


Step 2: Configure the Application Properties

Open the application.properties file and add the configuration for the MySQL database

spring.application.name=security-prevent-bruteforce

MySQL database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/securityUser
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Hibernate properties
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.thymeleaf.cache=false
spring.main.allow-circular-references=true

This configures the MySQL database connection and Hibernate properties.

Step 3: Create the SecurityConfig class

Create the SecurityConfig class that implements the security configuration of the Spring Boot application.

Go to src > main > java > com.demo.springpreventbruteforce > config > SecurityConfig and enter the below code.

Java
package com.demo.securitypreventbruteforce.config;

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.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CustomAuthenticationProvider authenticationProvider;

    public SecurityConfig(CustomAuthenticationProvider authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests
                                .requestMatchers("/login", "/blocked").permitAll() // Allow access to login and blocked pages
                                .anyRequest().authenticated() // All other requests require authentication
                )
                .formLogin(formLogin ->
                        formLogin
                                .loginPage("/login").permitAll() // Use custom login page
                                .defaultSuccessUrl("/home", true) // Redirect to home on successful login
                )
                .logout(logout ->
                        logout.permitAll() // Allow logout for all users
                )
                .authenticationProvider(authenticationProvider); // Use custom authentication provider

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // Use BCrypt for password encoding
    }

    @Bean
    public UserDetailsService userDetailsService() {
        var userDetailsService = new InMemoryUserDetailsManager();
        var user = User.withUsername("user")
                .password(passwordEncoder().encode("password"))
                .roles("USER")
                .build();
        userDetailsService.createUser(user);
        return userDetailsService; // Set up in-memory user details service
    }
}

Explanation:

  • Configures the Spring Security with custom login and logout pages, permiting access to the /login and /blocked while securing the other endpoints of the Spring application.
  • We use the custom authentication provider CustomAuthenticationProvider for handling the authentication logic.
  • Implements the password encoding with BCryptPasswordEncoder for securing the password hashing.
  • Set up the in-memory user details service with the single user for basic authentication.

Step 4: Create the CustomAuthenticationProvider class

Create the CustomAuthenticationProvider class that implements custom authentication logic.

Go to src > main > java > com.demo.springpreventbruteforce > config > CustomAuthenticationProvider and enter the below code.

Java
package com.demo.securitypreventbruteforce.config;

import com.demo.securitypreventbruteforce.service.BruteForceProtectionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.stereotype.Component;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private BruteForceProtectionService bruteForceProtectionService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();

        // Check if the user is blocked due to too many failed login attempts
        if (bruteForceProtectionService.isBlocked(username)) {
            throw new BadCredentialsException("You have been temporarily locked due to too many failed login attempts.");
        }

        User user = (User) userDetailsService.loadUserByUsername(username);

        // Verify user credentials
        if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
            bruteForceProtectionService.loginFailed(username); // Record failed login attempt
            throw new BadCredentialsException("Invalid username or password.");
        }

        bruteForceProtectionService.loginSucceeded(username); // Record successful login
        return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

Explanation:

  • Brute Force Protection: Checks of the user is blocked due to too many failed login attempts using BruteForceProtectionService and blocks the authentication if necessary.
  • User Authentication: Retrieves the user details and verifies the password using the userDetailsService and PasswordEndcoder.
  • Login Attempts Tracking: It calls the BruteForceProtectionService to record the failed or successful login attempts based on the authentication result of the application.
  • Authentication Provider Support: It specifies that this provider supports the UsernamePasswordAuthenticationToken for authentication.

Step 5: Create the BruteForceProtectionService Class

Create the BruteForceProtectionService class to handle tracking and blocking of failed login attempts.

Go to src > main > java > com.demo.springpreventbruteforce > service > BruteForceProtectionService and enter the below code.

Java
package com.demo.securitypreventbruteforce.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.Map;

@Service
public class BruteForceProtectionService {

    private static final int MAX_ATTEMPT = 5;
    private static final long LOCK_TIME = TimeUnit.MINUTES.toMillis(15);

    private final Map<String, Integer> attemptsCache = new ConcurrentHashMap<>();
    private final Map<String, Long> lockCache = new ConcurrentHashMap<>();

    public void loginSucceeded(String key) {
        attemptsCache.remove(key); // Clear failed attempts on successful login
        lockCache.remove(key); // Unlock user on successful login
    }

    public void loginFailed(String key) {
        int attempts = attemptsCache.getOrDefault(key, 0);
        attempts++;
        attemptsCache.put(key, attempts);
        if (attempts >= MAX_ATTEMPT) {
            lockCache.put(key, System.currentTimeMillis()); // Lock user if max attempts exceeded
        }
    }

    public boolean isBlocked(String key) {
        if (!lockCache.containsKey(key)) {
            return false;
        }

        long lockTime = lockCache.get(key);
        if (System.currentTimeMillis() - lockTime > LOCK_TIME) {
            lockCache.remove(key); // Remove lock if lock time has expired
            return false;
        }

        return true; // User is still locked
    }
}

Explanation:

  • Max Attempts and Lock Time: Defines the maximum allowed the login attempts MAX_ATTEMPT = 5 and lock duration LOCK_TIME = 15 minutes for the failed login attempts.
  • Login Success Handling: It clears the failed attempts and unlocks the user by removing their entries from attemptsCache and lockCache.
  • Login Failure Handling: It increments the failed attempts to count for the user in attemptsCache. If attempts the exceed the maximum limit, records the current time to lock the user.
  • Block Check: It determines if user is currently blocked by checking if the lock time has expired. If the lock time has passed then the user is removed from the lockCache and no longed blocked.

Step 6: Create the AuthController Class

Create the AuthController class to handle login requests.

Go to src > main > java > com.demo.springpreventbruteforce > controller > AuthController and put the below code.

Java
package com.demo.securitypreventbruteforce.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AuthController {

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

This controller handles the GET request for the login page.

Step 7: Create the HomeController Class

Create the HomeController class to handle home and blocked pages.

Go to src > main > java > com.demo.springpreventbruteforce > controller > HomeController and enter the below code.

Java
package com.demo.securitypreventbruteforce.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/home")
    public String home(Model model) {
        return "home"; // Return home view
    }

    @GetMapping("/blocked")
    public String blocked(Model model) {
        return "blocked"; // Return blocked view
    }
}

This controller handles the GET requests for the home and blocked pages.

Step 8: Main Class

No changes are required in the main class.

Java
package com.demo.securitypreventbruteforce;

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

@SpringBootApplication
public class SecurityPreventBruteforceApplication {

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

}

This is the main entry point for the Spring Boot application.

Step 9: Create the Login HTML page

Create the login.html page for the login view.

Go to src > main > resources > templates > login.html and enter the below HTML code.

HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        body {
            background-color: #f5f5f5;
            font-family: Arial, sans-serif;
        }
        .login-container {
            max-width: 400px;
            margin: 0 auto;
            padding-top: 100px;
        }
        .card {
            border: 1px solid #e0e0e0;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .card-header {
            background-color: lightgreen;
            color: white;
        }
        .btn-primary {
            background-color: lightgreen;
            border-color: lightgreen;
        }
        .btn-primary:hover {
            background-color: #32cd32;
            border-color: #32cd32;
        }
    </style>
</head>
<body>
<div class="login-container">
    <div class="card">
        <div class="card-header text-center">
            <h3>Login</h3>
        </div>
        <div class="card-body">
            <form th:action="@{/login}" method="post">
                <div class="form-group">
                    <label for="username">Username:</label>
                    <input type="text" class="form-control" id="username" name="username" required>
                </div>
                <div class="form-group">
                    <label for="password">Password:</label>
                    <input type="password" class="form-control" id="password" name="password" required>
                </div>
                <div class="form-group text-center">
                    <button type="submit" class="btn btn-primary btn-block">Login</button>
                </div>
            </form>
        </div>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

This HTML page creates a simple login form with Bootstrap styling.

Step 10: Create the Home HTML page

Create the home.html page for the home page

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

HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        body {
            background-color: #f5f5f5;
            font-family: Arial, sans-serif;
        }
        .home-container {
            max-width: 400px;
            margin: 0 auto;
            padding-top: 100px;
        }
        .card {
            border: 1px solid #e0e0e0;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .card-header {
            background-color: lightgreen;
            color: white;
        }
    </style>
</head>
<body>
<div class="home-container">
    <div class="card">
        <div class="card-header text-center">
            <h3>Welcome Home</h3>
        </div>
        <div class="card-body">
            <p>Welcome! You have successfully logged in.</p>
        </div>
    </div>
</div>
</body>
</html>

The home.html page displays a welcome message upon successful login. It uses Bootstrap for styling and includes a card component with a header and body to present the message.

Step 11: Create the Block HTML page

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

HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Account Locked</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        body {
            background-color: #f5f5f5;
            font-family: Arial, sans-serif;
        }
        .blocked-container {
            max-width: 400px;
            margin: 0 auto;
            padding-top: 100px;
        }
        .card {
            border: 1px solid #e0e0e0;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .card-header {
            background-color: lightgreen;
            color: white;
        }
    </style>
</head>
<body>
<div class="blocked-container">
    <div class="card">
        <div class="card-header text-center">
            <h3>Account Locked</h3>
        </div>
        <div class="card-body">
            <p>Your account has been temporarily locked due to too many failed login attempts. Please try again later.</p>
        </div>
    </div>
</div>
</body>
</html>

The block.html page informs the user that their account has been temporarily locked due to too many failed login attempts. It also uses Bootstrap for styling and displays the message in a card component.

pom.xml file:

XML
<?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>3.3.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.demo</groupId>
    <artifactId>security-prevent-bruteforce</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security-prevent-bruteforce</name>
    <description>security-prevent-bruteforce</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</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-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 12: Run the application

Run the application, which will start on port 8080.

Application Started


Step 13: Testing the Application

Login Page:

Navigate to http://localhost:8080/login. If the credentials are correct, it will redirect to the home page.

http://localhost:8080/login

Output:

Login Page


Home Page:

Navigate to http://localhost:8080/home to view the home page after successful login.

http://localhost:8080/home

Output:

Home Page

If you are trying multiple times, then your account will be temporarily blocked.


Blocked Page:

If multiple failed login attempts are made, the account will be temporarily locked, and the user will be redirected to http://localhost:8080/block.

http://localhost:8080/block

Output:

Blocked Page

Conclusion

With this setup, we have the Spring Boot application that can protect against brute force attacks by temporarily locking the user accounts after the number of failed login attempts of the application. This basic implementation can be extended to use the database to store user details and failed login attempts for the better scalability of the Spring Boot application.




Reffered: https://www.geeksforgeeks.org


Advance Java

Related
Implementing Request Response in Java Apache Kafka Implementing Request Response in Java Apache Kafka
ETL with Spring Cloud Data Flow ETL with Spring Cloud Data Flow
Migrating a Spring Boot Application from Spring Security 5 to Spring Security 6 Migrating a Spring Boot Application from Spring Security 5 to Spring Security 6
Spring Security - Updating Your Password Spring Security - Updating Your Password
Resolving Failed to Configure a DataSource Error in Spring Boot Application Resolving Failed to Configure a DataSource Error in Spring Boot Application

Type:
Geek
Category:
Coding
Sub Category:
Tutorial
Uploaded by:
Admin
Views:
13