With the recent surge in security vulnerabilities across the Spring ecosystem in the first half of 2026, relying on scattered security validation inside individual REST controllers is no longer an option—especially for banking and financial applications. Security must be tight, centralized, and fully auditable.
In this article, we will look at how to build an enterprise-grade API architecture that secures endpoints globally using a HandlerInterceptor and automatically logs every user transaction into a PostgreSQL database for a bulletproof audit trail.
1. Centralizing Request Validation with a HandlerInterceptor
Instead of repeating token extraction logic in every controller method, we can intercept incoming HTTP requests globally. This approach keeps our controllers thin and focused purely on routing.
First, let's create a centralized security interceptor that validates the token and extracts the necessary audit data:
@Component
public class SecurityAuditInterceptor implements HandlerInterceptor {
@Autowired
private AuditLogService auditLogService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authToken = request.getHeader("Authorization");
// Simple token extraction logic
if (authToken == null || !authToken.startsWith("Bearer ")) {
throw new UnauthorizedException("Missing or invalid Authorization header");
}
String jwtToken = authToken.substring(7);
String userId = CommonUtil.extractUserIdFromToken(jwtToken);
// Store extracted information in the request attribute for later audit logging
request.setAttribute("currentUserId", userId);
request.setAttribute("actionTime", LocalDateTime.now());
return true; // Proceed to the controller
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String userId = (String) request.getAttribute("currentUserId");
String action = request.getMethod() + " " + request.getRequestURI();
String status = (ex == null) ? "SUCCESS" : "FAILED: " + ex.getMessage();
if (userId != null) {
// Log the action asynchronously into PostgreSQL to ensure compliance
auditLogService.saveLog(userId, action, status);
}
}
}
Next, register this interceptor inside your WebMvc configuration:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private SecurityAuditInterceptor securityAuditInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(securityAuditInterceptor)
.addPathPatterns("/api/v1/finance/**"); // Protect all finance endpoints
}
}
2. Designing the Asynchronous Audit Trail Service
An audit log must never slow down your main API response times. To achieve this, the service handling the PostgreSQL persistence layer should execute asynchronously.
Here is how to design the service pattern using Spring's @async:
@Service
public class AuditLogService {
@Autowired
private AuditLogRepository auditLogRepository;
@Async
@Transactional
public void saveLog(String userId, String action, String status) {
AuditLogEntity log = AuditLogEntity.builder()
.userId(userId)
.action(action)
.status(status)
.timestamp(LocalDateTime.now())
.build();
auditLogRepository.save(log);
// Output to secure internal log management system
System.out.println("AUDIT TRAIL LOGGED | User: " + userId + " | Action: " + action);
}
}
3. Masking Sensitive Enterprise Data via DTOs
When processing multi-layered, nested financial structures (like transaction histories or asset calculations), never expose your underlying database entities to the client. This prevents accidental data leaks and decouples your database schema from the API contract.
Always map your database entities to tight, unmodifiable Data Transfer Objects (DTOs):
@Data
@Builder
public class TransactionSummaryDto {
private String referenceNumber;
private BigDecimal totalAmount;
private List<ItemDetailDto> items;
}
@Data
@Builder
public class ItemDetailDto {
private String itemId;
private String itemName;
private BigDecimal price;
}
4. Enforcing Predictable API Responses
A truly resilient API handles errors gracefully and keeps the response schema uniform. Whether a transaction succeeds or a security exception is thrown, the frontend client should receive a clean JSON wrapper.
@Data
@AllArgsConstructor
public class BaseResponse<T> {
private String status;
private String message;
private T data;
public static <T> BaseResponse<T> success(T data) {
return new BaseResponse<>("SUCCESS", "Transaction completed successfully", data);
}
public static <T> BaseResponse<T> error(String message) {
return new BaseResponse<>("ERROR", message, null);
}
}
Conclusion
As cybersecurity requirements tighten globally for financial services, building robust, audit-ready applications is no longer optional. By coupling global request interceptors with asynchronous PostgreSQL logging and strict data boundaries via DTOs, you protect your system against vulnerabilities while making life significantly easier for your frontend developers.
Top comments (0)