Logging Successful and Failed Authentication Attempts with Spring Security

Authentication is a critical component of any application's security. It verifies the identity of users and allows them access to protected resources. As developers, it is important for us to keep track of successful and failed authentication attempts to enhance the security of our applications. In this article, we will explore how to log these attempts using Spring Security, a powerful framework that simplifies authentication and authorization in Java applications.

Setting up Logging

Before we dive into logging successful and failed authentication attempts, let's first make sure we have the necessary dependencies in our project. Add the following dependencies to your Maven or Gradle build file:

// Maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

// Gradle
implementation 'org.springframework.boot:spring-boot-starter-security'

Spring Security provides a default logging mechanism that logs authentication events to the console. However, this is not suitable for production environments where we need structured and persistent logs. To achieve this, we will integrate a logging framework like Logback or Log4j into our application.

In this example, we will use Logback as the logging framework. Add the following dependency to your build file:

// Maven
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
</dependency>

// Gradle
implementation 'ch.qos.logback:logback-classic'

Next, create a logback-spring.xml file in the src/main/resources directory of your project. This file contains the configuration for Logback. Below is a sample configuration to log authentication events:

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/authentication.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/authentication.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="org.springframework.security" level="INFO" additivity="false">
        <appender-ref ref="FILE" />
    </logger>

    <root level="INFO">
        <appender-ref ref="FILE" />
    </root>
</configuration>

This configuration creates a rolling file appender that writes authentication logs to a file named authentication.log in the logs directory of your project. The logs are rolled daily (with a maximum of 30 files) and can grow up to 2GB in size. The log format includes the date, log level, thread, logger name, and message.

Logging Successful Authentication Attempts

To log successful authentication attempts, we can leverage Spring Security's AuthenticationSuccessHandler. This component is called whenever a user successfully authenticates. Let's create a custom success handler and configure it to log the authentication event.

Create a new class called CustomAuthenticationSuccessHandler:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        logger.info("Successful authentication for user: {}", authentication.getName());
        
        // Additional logic can be added here if needed

        // Redirect the user to a specific page
        response.sendRedirect("/home");
    }
}

In this class, we implement the AuthenticationSuccessHandler interface and override the onAuthenticationSuccess method. Inside this method, we log the successful authentication event using the configured logger. You can add additional logic here, such as updating the user's last login timestamp or handling any custom actions.

Next, we need to configure Spring Security to use our custom success handler. Open the SecurityConfig class (or any configuration class where you have defined security configurations) and update it as follows:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .successHandler(customAuthenticationSuccessHandler)
                .and()
            .logout()
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .logoutSuccessUrl("/login?logout");
    }
}

In this configuration, we configure the success handler using the successHandler method of the formLogin builder. Spring Security will call our CustomAuthenticationSuccessHandler when a user successfully authenticates.

Logging Failed Authentication Attempts

Similarly, we can log failed authentication attempts by implementing the AuthenticationFailureHandler interface provided by Spring Security. Let's create a CustomAuthenticationFailureHandler class for this purpose:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        if (exception instanceof BadCredentialsException) {
            logger.warn("Failed authentication attempt with bad credentials");
        } else {
            logger.warn("Failed authentication attempt");
        }

        // Redirect the user to a specific error page
        response.sendRedirect("/login?error");
    }
}

In this class, we implement the AuthenticationFailureHandler interface and override the onAuthenticationFailure method. We check if the exception is of type BadCredentialsException to log whether the failure was due to bad credentials. Again, you can add additional logic or handle custom actions here.

To configure Spring Security to use our custom failure handler, update the SecurityConfig class as follows:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .successHandler(customAuthenticationSuccessHandler)
                .failureHandler(customAuthenticationFailureHandler)
                .and()
            .logout()
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .logoutSuccessUrl("/login?logout");
    }
}

In this configuration, we configure the failure handler using the failureHandler method of the formLogin builder. Spring Security will call our CustomAuthenticationFailureHandler when a user fails to authenticate.

Conclusion

In this article, we have learned how to log successful and failed authentication attempts in a Spring Security-enabled application. By logging authentication events, we can monitor and investigate any unauthorized access attempts, helping us identify and mitigate security vulnerabilities. Logging is a crucial aspect of securing our applications, and with Spring Security's powerful integration with logging frameworks, we can ensure better visibility into our authentication processes.

© NoobToMaster - A 10xcoder company