Spring Security - Authentication Failure Handler



Spring Security allows us to customize our authentication process as much as we want. Starting from a custom login page to our very own customized authentication providers and authentication filters, we can pretty much customize every aspect of the authentication process. We can define our own authentication process which can range from basic authentication using a username and a password to a complex one such as two-factor authentication using tokens and OTPs. We can customize authentication failure handling as well. By default, spring security redirects user to login page where HttpRequest object contains the error information.

Authentication Failure Handler

Spring Security provides AuthenticationFailureHandler interface with onAuthenticationFailure() method. We can use inbuilt handler implementations or create our own handler to customize the onAuthenticationFailure() method implementation.

public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
   @Override
   public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException exception) throws IOException, ServletException {
      response.setStatus(HttpStatus.UNAUTHORIZED.value());
      response.sendRedirect("/error");
   }
}

Here we're sending http status UNAUTHORIZED or 401 and redirecting user to the error page.

AuthenticationFailureHandler Implementations

Spring Security provides following major implementations of AuthenticationFailureHandler.

  • SimpleUrlAuthenticationFailureHandler − This is the default authentication failure handler. It redirects user to the failureUrl if specified, otherwise 401 response is returned as UNAUTHORIZED.

  • ForwardAuthenticationFailureHandler − This authentication failure handler forwards the user to provided url.

  • ExceptionMappingAuthenticationFailureHandler − This authentication failure handler forwards the user to provided url on the basis of name of the AuthenticationException.

  • DelegatingAuthenticationFailureHandler − This authentication failure handler allows to use other AuthenticationFailureHandler implementations on the basis of the AuthenticationException instances.

We'll be creating our own AuthenticationFailureHandler in coming section.

Spring Security Configuration

We need to configure the CustomAuthenticationFailureHandler in spring securty configuration

http
//...
.formLogin(form -> form.loginPage("/login")
   .defaultSuccessUrl("/")
   .failureHandler(new CustomAuthenticationFailureHandler())
   .permitAll())    
...
}

That's all we need. Now let's see the complete code in action.

Before you start writing your first example using Spring framework, you have to make sure that you have set up your Spring environment properly as explained in Spring Security - Environment Setup Chapter. We also assume that you have a bit of working knowledge on Spring Tool Suite IDE.

Now let us proceed to write a Spring MVC based Application managed by Maven, which will ask user to login, authenticate user and then provide option to logout using Spring Security Form Login Feature.

Create Project using Spring Initializr

Spring Initializr is great way to start with Spring Boot project. It provides a easy to use User Interface to create a project, add dependencies, select java runtime etc. It generates a skeleton project structure which once downloaded can be imported in spring tool suite and we can proceed with our readymade project structure.

We're choosing a maven project, naming the project as formlogin, with java version as 21. Following dependencies are added:

  • Spring Web

  • Spring Security

  • Thymeleaf

  • Spring Boot DevTools

Spring Initializr

Thymeleaf is a templating engine for Java. It allows us to quickly develop static or dynamic web pages for rendering in the browser. It is extremely extensible and allows us to define and customize the processing of our templates in fine detail. In addition to this, we can learn more about Thymeleaf by clicking this link.

Let's move on to generate our project and download it. We then extract it to a folder of our choice and use any IDE to open it. I shall be using Spring Tools Suite 4. It is available for free downloading from the https://spring.io/tools website and is optimized for spring applications.

pom.xml with all relevant dependencies

Let's take a look at our pom.xml file. It should look something similar to this −

pom.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.tutorialspoint.security</groupId>
   <artifactId>formlogin</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>formlogin</name>
   <description>Demo project for Spring Boot</description>
   <url/>
   <licenses>
      <license/>
   </licenses>
   <developers>
      <developer/>
   </developers>
   <scm>
      <connection/>
      <developerConnection/>
      <tag/>
      <url/>
   </scm>
   <properties>
      <java.version>21</java.version>
   </properties>
   <dependencies>
      <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>org.thymeleaf.extras</groupId>
         <artifactId>thymeleaf-extras-springsecurity6</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-devtools</artifactId>
         <scope>runtime</scope>
         <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>
         </plugin>
      </plugins>
   </build>
</project>

AuthenticationFailureHandler

Inside of our config package, we have created the CustomAuthenticationFailureHandler class by implementing AuthenticationFailureHandler interface. As in onAuthenticationFailure() method, we're setting HTTP Status as UNAUTHORIZED and redirecting user to the error page.

package com.tutorialspoint.security.formlogin.config;

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
   @Override
   public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException exception) throws IOException, ServletException {
      response.setStatus(HttpStatus.UNAUTHORIZED.value());
      response.sendRedirect("/error");
   }
}

Spring Security Configuration Class

Inside of our config package, we have created the WebSecurityConfig class. We shall be using this class for our security configurations, so let's annotate it with an @Configuration annotation and @EnableWebSecurity. As a result, Spring Security knows to treat this class a configuration class. As we can see, configuring applications have been made very easy by Spring.

WebSecurityConfig

package com.tutorialspoint.security.formlogin.config; 

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.AuthenticationException;
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;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; 

@Configuration 
@EnableWebSecurity
public class WebSecurityConfig {

   @Bean 
   protected UserDetailsService userDetailsService() {
      UserDetails user = User.builder()
         .username("user")
         .password(passwordEncoder().encode("user123"))
         .roles("USER")
         .build();
      UserDetails admin = User.builder()
         .username("admin")
         .password(passwordEncoder().encode("admin123"))
         .roles("USER", "ADMIN")
         .build();
      return new InMemoryUserDetailsManager(user, admin);
   }

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

   @Bean
   protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 
      return http
         .csrf(AbstractHttpConfigurer::disable)
         .authorizeHttpRequests(
            request -> request.requestMatchers("/login").permitAll()
            .requestMatchers("/error").permitAll()
            .requestMatchers("/**").authenticated()
         )
         .formLogin(form -> form.loginPage("/login")
            .defaultSuccessUrl("/")
            .failureHandler(new CustomAuthenticationFailureHandler())
            .permitAll())        
            .logout(config -> config  
            .logoutUrl("/logout") 
            .logoutSuccessUrl("/login")) 
      .build();
   }   
}

Configuration Class Details

Let's take a look at our configuration class.

  • First, we shall create a bean of our UserDetailsService class by using the userDetailsService() method. We shall be using this bean for managing our users for this application. Here, to keep things simple, we shall use an InMemoryUserDetailsManager instance to create users. These users, along with our given username and password, are mapped to User and Admin roles respectively.

Http Security Configuration

After the above steps, we move on to our next configuration. Here, we've defined the filterChain method. This method takes HttpSecurity as a parameter. We shall be configuring this to use our form login and logout function.

We can observe that all these functionalities are available in Spring Security. Lets study the below section in detail −

return http
//...
 .failureHandler(new CustomAuthenticationFailureHandler())
//...
    .build();

Here we're added a authentication failure handler as CustomAuthenticationFailureHandler instance.

Controller Class

In this class, we've created a mapping for single "/" endpoint for the index page of this application, for simplicity. This will redirect to index.html.

AuthController

package com.tutorialspoint.security.formlogin.controllers; 

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

@Controller 
public class AuthController { 
   @GetMapping("/") 
   public String home() { 
      return "index"; 
   }
}

Views

Create index.html in /src/main/resources/templates folder with following content to act as a home page.

index.html

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6"> 
   <head> 
      <title>
         Hello World!
      </title> 
   </head>
   <body> 
      <h1 th:inline="text">Hello World!</h1> 
      <a href="/logout" alt="logout">Sign Out</a>
   </body> 
<html> 

Create error.html in /src/main/resources/templates folder with following content to act as an error page.

error.html

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6"> 
   <head> 
      <title>
      Invalid Credentials
      </title> 
   </head>
   <body> 
      <h1>Invalid Credentials.</h1>
   </body> 
<html> 

Running the Application

As we've all component ready, let's run the Application. Right Click on the project, select Run As and then Spring Boot App.

It will boot up the application and once application is started, we can run localhost:8080 to check the changes.

Output

Now open localhost:8080, you can see the login page.

Enter Invalid Credential

Form Authentication

Error Page

If we enter invalid credential, then user will be redirected to error page.

Form Authentication with Invalid Credential
Advertisements