Introduction

In web applications, authentication plays a crucial role in verifying user credentials and granting access to authorized resources. Spring Security provides a robust authentication framework with a default implementation for handling authentication failures. However, there may be scenarios where you need to customize the behavior of authentication failure handling to meet specific requirements.

In this article, we’ll explore how to create a custom AuthenticationFailureHandler in a Spring Boot application and integrate it with Spring Security. We’ll cover the necessary dependencies, code examples, and a testing section to help you understand and implement this feature effectively.

Dependencies

Before we dive into the implementation details, let’s add the required dependencies to our pom.xml file:

XML
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>6.0.0</version>
      <scope>provided</scope>
    </dependency>

    <!-- Other dependencies -->
</dependencies>

We’ve included the spring-boot-starter-web and spring-boot-starter-security dependencies for building web applications and integrating Spring Security. Additionally, we’ve added the jackson-databind dependency for JSON serialization and deserialization, which we’ll use in our custom AuthenticationFailureHandler implementation.

Custom AuthenticationFailureHandler Implementation

Spring Security provides the AuthenticationFailureHandler interface, which allows you to implement custom behavior for handling authentication failures. Here’s an example implementation of a custom AuthenticationFailureHandler:

Java
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        Map<String, Object> data = new HashMap<>();
        data.put("timestamp", Calendar.getInstance().getTime());
        data.put("exception", exception.getMessage());

        response.getOutputStream().println(objectMapper.writeValueAsString(data));
    }
}

In this implementation, we create a CustomAuthenticationFailureHandler class that implements the AuthenticationFailureHandler interface. The onAuthenticationFailure method is overridden to provide custom behavior for handling authentication failures.

When an authentication failure occurs, this method is called with the HttpServletRequest, HttpServletResponse, and the AuthenticationException that caused the failure. In our implementation, we set the response status code to 401 Unauthorized, create a map with the timestamp and exception message, and write the JSON representation of this map to the response output stream using the ObjectMapper from the jackson-databind library.

Spring Security Configuration

To integrate the custom AuthenticationFailureHandler with Spring Security, we need to configure it in our SecurityConfig class:

Java
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.UserDetails;
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 {

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("user")
                .password(passwordEncoder().encode("password"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(user);
    }

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

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests()
                .requestMatchers("/login", "/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .failureHandler(new CustomAuthenticationFailureHandler())
                .and()
                .logout()
                .logoutUrl("/logout")
                .permitAll();

        return http.build();
    }
}

In this configuration class, we define beans for the UserDetailsService, PasswordEncoder, and SecurityFilterChain. We’re using an in-memory user details manager for simplicity, but in a real-world application, you would typically use a persistent data store like a database.

The important part for our custom AuthenticationFailureHandler implementation is the securityFilterChain method, where we configure Spring Security. We set up form-based authentication and specify our CustomAuthenticationFailureHandler instance as the failureHandler for authentication failures.

Testing

To test the custom AuthenticationFailureHandler, we can use a tool like Postman or cURL to send an HTTP request with invalid credentials and observe the response.

Here’s an example using cURL:

curl -X POST -F 'username=user' -F 'password=wrongpassword' http://localhost:8080/login

This command sends a POST request to the /login endpoint with the username user and an incorrect password wrongpassword.

If the authentication fails, you should see a response similar to the following:

{"timestamp":"2023-06-15T14:25:30.123+0000","exception":"Bad credentials"}

This response indicates that the custom AuthenticationFailureHandler is working as expected, returning a JSON response with the timestamp and the exception message.

Conclusion:

In this article, we explored how to create a custom AuthenticationFailureHandler in a Spring Boot application and integrate it with Spring Security. We covered the necessary dependencies, provided a code example for the custom AuthenticationFailureHandler implementation, and demonstrated how to configure it in the SecurityConfig class.

By implementing a custom AuthenticationFailureHandler, you can tailor the behavior of authentication failure handling to meet your application’s specific requirements, such as providing a different response format, logging additional information, or triggering additional actions.

The flexibility of Spring Security allows you to extend and customize various aspects of the authentication and authorization mechanisms, making it a powerful and versatile framework for securing your web applications.

By |Last Updated: May 25th, 2024|Categories: Spring Security|