Audit logging is a critical component of any application's security and compliance strategy. It helps track user activities, detect unauthorized access, and maintain an audit trail for regulatory requirements. In this tutorial, we will explore how to implement audit logging in a Spring Boot application using the spring-boot-starter-audit module.
Spring Boot provides built-in support for auditing through the spring-boot-starter-audit module. This module leverages Spring's AuditEventRepository and AuditTrail to capture and store audit events. We will cover how to set up audit logging, customize event details, and integrate with various storage solutions like a database or file system.
First, create a new Spring Boot project using Spring Initializr. Select the following dependencies:
Download and import the project into your IDE.
To enable audit logging, add the @EnableWebSecurity annotation to your security configuration class. This will automatically configure Spring Security with auditing support.
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
Spring Boot provides an in-memory implementation of AuditEventRepository by default. However, for production use, you might want to store audit events in a database or file system.
First, add the necessary dependencies to your pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Next, create an entity class for storing audit events:
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "audit_event")
@EntityListeners(AuditingEntityListener.class)
public class AuditEvent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String principal;
@Column(nullable = false)
private Date createdDate;
@Column(length = 255)
private String type;
@Column(length = 1024)
private String data;
// Getters and setters
}
Then, implement a custom AuditEventRepository:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class JpaAuditEventRepository implements AuditEventRepository {
@Autowired
private AuditEventRepository auditEventRepository;
@Override
public List<AuditEvent> find(String principal, Date after) {
return auditEventRepository.findByPrincipalAndCreatedDateAfter(principal, after)
.stream()
.map(this::convertToAuditEvent)
.collect(Collectors.toList());
}
@Override
public List<AuditEvent> find(Date after, String type) {
return auditEventRepository.findByCreatedDateAfterAndType(after, type)
.stream()
.map(this::convertToAuditEvent)
.collect(Collectors.toList());
}
@Transactional
@Override
public void add(AuditEvent event) {
AuditEventEntity entity = new AuditEventEntity();
entity.setPrincipal(event.getPrincipal());
entity.setCreatedDate(event.getTimestamp());
entity.setType(event.getType());
entity.setData(String.valueOf(event.getData()));
auditEventRepository.save(entity);
}
private AuditEvent convertToAuditEvent(AuditEventEntity entity) {
return new AuditEvent(entity.getCreatedDate(), entity.getPrincipal(), entity.getType(),
entity.getData() != null ? entity.getData().split(",") : new String[0]);
}
}
For simpler use cases, you can store audit events in a file. Create a custom AuditEventRepository:
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.stereotype.Component;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.List;
@Component
public class FileAuditEventRepository implements AuditEventRepository {
private static final String FILE_PATH = "audit.log";
@Override
public List<AuditEvent> find(String principal, Date after) {
// Implement file reading logic here
return null;
}
@Override
public List<AuditEvent> find(Date after, String type) {
// Implement file reading logic here
return null;
}
@Transactional
@Override
public void add(AuditEvent event) {
try (FileWriter writer = new FileWriter(FILE_PATH, true)) {
writer.write(String.format("%s,%s,%s,%s%n", event.getTimestamp(), event.getPrincipal(),
event.getType(), String.join(",", event.getData())));
} catch (IOException e) {
e.printStackTrace();
}
}
}
You can customize audit events by creating a custom AuditAware implementation:
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional;
@Component
public class SpringSecurityAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.empty();
}
return Optional.of(authentication.getName());
}
}
To automatically log audit events for security-related actions, you can extend WebSecurityConfigurerAdapter and configure auditing:
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
To test audit logging, you can create a simple REST controller and perform some actions:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test")
public String test() {
return "Test endpoint";
}
}
Start your Spring Boot application and access the /test endpoint. You should see audit events being logged in your chosen storage solution.
Audit logging is a powerful tool for maintaining security and compliance in your Spring Boot applications. By leveraging Spring's built-in auditing support and customizing event storage and details, you can create a robust audit trail that meets your organization's needs.