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.
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.
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.
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.
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.
noob to master © copyleft