Database Auditing in Spring boot with spring security context and spring data JPA
Introduction
Auditing in a database involves keeping track of the user who performs certain operations on the data, such as inserting, modifying, or deleting entries, as well as the time those actions were taken. This information can be useful for various purposes, such as security and compliance. In Spring Boot, auditing can be implemented using the @CreatedDate
, @CreatedBy
, @LastModifiedDate
, and @LastModifiedBy
annotations, as well as the AuditingEntityListener and AuditorAware interfaces.
More on auditing annotations
@CreatedDate
is used to automatically set the creation date of an entity. When a new entity is persisted, the current date and time will be automatically set as the creation date.
@CreatedBy
is used to automatically set the username of the user who created an entity. When a new entity is persisted, the username of the currently authenticated user will be automatically set as the creator.
@LastModifiedDate
is used to automatically set the last modified date of an entity. Whenever an entity is updated, the current date and time will be automatically set as the last modified date.
@LastModifiedBy
annotation is used to automatically update the field that represents the last modifier of an entity when it is modified.
Apart from these there some other annotations also available, and helpful in extending the auditing functionality
@PrePersist
is used to mark a method that is executed automatically before an entity is persisted (inserted) into the database.
@PreUpdate
is used to mark a method that is automatically executed before an entity is updated (changes are synchronised) with the database.
With the help of @PrePersist and @PreUpdate
, we can define custom logic to be executed inside annotated method, such as setting default values or performing any other actions on the entity before it is saved.
In this article we will learn how to put these annotations and interfaces to work and automatically update the auditor details in the database.
Goal
Main goal of this example is to demonstrate how we can update certain columns (related to auditing like createdBy, createdOn, updatedBy, updatedOn) in a table of database by utilising the spring’s auditing feature and security context.
Source code
mayank042/spring-jpa-auditing (github.com)
Pre-requisites
This example is based on assumption that Spring web security is configured in the application, as we will fetch the auditor (signed user) details from the security context. Or you can modify the implementation of AuditAware (see below) not to use the security context but get user details from other source.
You can see the security configuration implemented in the source code itself, will not talk much about that as it is out of the scope of this article.
Below is an basic example of how to set authenticated user details in the spring security context
// set authenticated userDeatils in security context
// userDeatils = instance of org.springframework.security.core.userdetails.UserDetails or can be a implementation of this class (where you can add your own required prop.
var userDetails = ...
var authentication = new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
Get your hands dirty
Dependencies
Start by adding the required maven dependencies -
Spring data JPA — is a powerful framework that simplifies working with relational databases in Java applications by providing automated CRUD operations, query generation, and repository support with minimal code.
Spring Security — provides authentication, authorization, and other security features. It simplifies the implementation of user authentication, role-based access control, and protection against common security vulnerabilities.
<!-- pom.xml -->
<!--JPA-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring AuditorAware
AuditorAware
is an interface provided by Spring Data JPA that is used to dynamically determine the current auditor (user) for auditing purposes.
The AuditorAware
interface has a single method: getCurrentAuditor()
. This method is responsible for providing the current auditor's identification, such as the username or ID. The returned value is then automatically set in the corresponding auditing fields of the audited entities.
By implementing the AuditorAware
interface and overriding the getCurrentAuditor()
method, we can customize the logic to determine the current auditor based on our application's requirements. For example, we may retrieve the auditor information from the currently logged-in user, from a security context, or from any other source.
In this example we retrieve information from security context.
Create an class implementing the AuditorAware interface and override method getCurrentAuditor
to return logged-in user id.
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// todo: add checks if authentication is present and user is not null (i.e. signed in).
SignedUser user = (SignedUser) authentication.getPrincipal();
return Optional.of(user.getUserId().toString());
}
}
Register our auditAware implementation so that spring can use it at runtime, we do it by creating a bean of it and enabling JPA auditing.
Create a configuration class annotated with @EnableJpaAuditing
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class Config {
@Bean
public AuditorAware<String> auditorProvider() {
// our implementation of AuditorAware
return new AuditorAwareImpl();
}
}
Note: value of auditorAwareRef is the name of the method annotated with
Bean
annotation.
We are done with all the configuration part now its time to use it.
As most of the tables in database will have the common auditing columns like createdBy, createdOn, updatedBy, updatedOn its not a best practice to put their related fields in all entity classes, instead of we can do some code reusability and created a common/base entity class with those fields and extend all other entity classes with that base entity class.
Creating an common Auditing entity
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditorEntity {
@CreatedDate
@Column(name = "CreatedOn")
private LocalDateTime createdOn;
@CreatedBy
@Column(name = "CreatedBy", length = 50)
private String createdBy;
@LastModifiedDate
@Column(name = "UpdatedOn")
private LocalDateTime updatedOn;
@LastModifiedBy
@Column(name = "UpdatedBy", length = 50)
private String updatedBy;
@Column(name = "DeletedOn")
private LocalDateTime deletedOn;
@Column(name = "DeletedBy", length = 50)
private String deletedBy;
@Column(name = "isDeleted", length = 50)
private Boolean isDeleted = false;
@PreUpdate
@PrePersist
public void beforeAnyUpdate() {
if (isDeleted != null && isDeleted) {
if (deletedBy == null) {
deletedBy = SignedUserHelper.userId().toString();
}
if (getDeletedOn() == null) {
deletedOn = LocalDateTime.now();
}
}
}
}
Pay attention on the annotation here @EntityListeners(AuditingEntityListener.class)
When you annotate an entity class with @EntityListeners
, you can specify one or more listener classes that implement the corresponding JPA callback methods. These methods will be invoked automatically when the specified events occur on the entity, such as entity creation, update, or deletion.
AuditingEntityListener.class
is a built-in entity listener class provided by Spring Data JPA for auditing purposes. It is used in conjunction with auditing annotations (@CreatedBy
, @LastModifiedBy
, @CreatedDate
, @LastModifiedDate
) to automatically populate auditing-related fields in entities.
Creating a real entity with auditing
@Entity
@Table(name = "user")
public class User extends AuditorEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Id", nullable = false)
private Long id;
@Column(name = "Email")
private String email;
@Column(name = "Name")
private String name;
@Column(name = "Phone")
private String phone;
}
This user entity extends the AuditorEntity which make auditing fields available to User entity.
Now whenever you create a new user —
createdBy — gets auto filled by currently logged in user
createdOn — gets auto filled by current date time
And on updating existing user -
updateBy — gets auto filled by currently logged in user
updateOn — gets auto filled by current date time
Keep in mind that this auditing functionality only works when we create or update entities using the JPA repository’s
save
method (not by writing native queries).