0
# Audit Framework
1
2
Spring Boot Actuator's audit framework provides event auditing capabilities for tracking authentication events, authorization failures, and custom application events with pluggable storage backends.
3
4
## Capabilities
5
6
### Core Audit API
7
8
Basic audit event representation and repository interface.
9
10
```java { .api }
11
/**
12
* Audit event representation
13
*/
14
public class AuditEvent implements Serializable {
15
16
/**
17
* Create audit event with current timestamp
18
*/
19
public AuditEvent(String principal, String type, Object... data);
20
21
/**
22
* Create audit event with specific timestamp
23
*/
24
public AuditEvent(Instant timestamp, String principal, String type, Map<String, Object> data);
25
26
/**
27
* Create audit event with string data
28
*/
29
public AuditEvent(String principal, String type, String... data);
30
31
/**
32
* Get the timestamp when the event occurred
33
*/
34
public Instant getTimestamp();
35
36
/**
37
* Get the principal (user) associated with the event
38
*/
39
public String getPrincipal();
40
41
/**
42
* Get the type of audit event
43
*/
44
public String getType();
45
46
/**
47
* Get the audit event data
48
*/
49
public Map<String, Object> getData();
50
51
@Override
52
public String toString();
53
}
54
```
55
56
**Usage Example:**
57
58
```java
59
@Service
60
public class UserService {
61
62
private final AuditEventRepository auditEventRepository;
63
64
public UserService(AuditEventRepository auditEventRepository) {
65
this.auditEventRepository = auditEventRepository;
66
}
67
68
public void loginUser(String username) {
69
// Business logic for login
70
performLogin(username);
71
72
// Create audit event
73
AuditEvent loginEvent = new AuditEvent(
74
username,
75
"USER_LOGIN",
76
"success", true,
77
"ip", getCurrentUserIP(),
78
"timestamp", Instant.now()
79
);
80
81
auditEventRepository.add(loginEvent);
82
}
83
84
public void loginFailed(String username, String reason) {
85
AuditEvent failedLoginEvent = new AuditEvent(
86
username,
87
"AUTHENTICATION_FAILURE",
88
"reason", reason,
89
"ip", getCurrentUserIP()
90
);
91
92
auditEventRepository.add(failedLoginEvent);
93
}
94
}
95
```
96
97
### Audit Event Repository
98
99
Interface and implementations for storing audit events.
100
101
```java { .api }
102
/**
103
* Repository interface for audit events
104
*/
105
public interface AuditEventRepository {
106
107
/**
108
* Add an audit event to the repository
109
*/
110
void add(AuditEvent event);
111
112
/**
113
* Find audit events for a principal after a specific time
114
*/
115
List<AuditEvent> find(String principal, Instant after, String type);
116
}
117
118
/**
119
* In-memory implementation of AuditEventRepository
120
*/
121
public class InMemoryAuditEventRepository implements AuditEventRepository {
122
123
public InMemoryAuditEventRepository();
124
public InMemoryAuditEventRepository(int capacity);
125
126
@Override
127
public void add(AuditEvent event);
128
129
@Override
130
public List<AuditEvent> find(String principal, Instant after, String type);
131
132
/**
133
* Find all audit events for a principal
134
*/
135
public List<AuditEvent> find(String principal);
136
137
/**
138
* Find audit events after a specific time
139
*/
140
public List<AuditEvent> find(Instant after);
141
142
/**
143
* Find audit events of a specific type
144
*/
145
public List<AuditEvent> find(String type);
146
}
147
```
148
149
**Usage Example:**
150
151
```java
152
@Configuration
153
public class AuditConfiguration {
154
155
@Bean
156
@ConditionalOnMissingBean(AuditEventRepository.class)
157
public InMemoryAuditEventRepository auditEventRepository() {
158
return new InMemoryAuditEventRepository(1000); // Keep last 1000 events
159
}
160
161
@Bean
162
@ConditionalOnProperty("app.audit.database.enabled")
163
public AuditEventRepository databaseAuditEventRepository(JdbcTemplate jdbcTemplate) {
164
return new DatabaseAuditEventRepository(jdbcTemplate);
165
}
166
}
167
168
// Custom database implementation
169
public class DatabaseAuditEventRepository implements AuditEventRepository {
170
171
private final JdbcTemplate jdbcTemplate;
172
173
public DatabaseAuditEventRepository(JdbcTemplate jdbcTemplate) {
174
this.jdbcTemplate = jdbcTemplate;
175
}
176
177
@Override
178
public void add(AuditEvent event) {
179
String sql = "INSERT INTO audit_events (timestamp, principal, type, data) VALUES (?, ?, ?, ?)";
180
jdbcTemplate.update(sql,
181
Timestamp.from(event.getTimestamp()),
182
event.getPrincipal(),
183
event.getType(),
184
convertDataToJson(event.getData())
185
);
186
}
187
188
@Override
189
public List<AuditEvent> find(String principal, Instant after, String type) {
190
StringBuilder sql = new StringBuilder("SELECT * FROM audit_events WHERE 1=1");
191
List<Object> params = new ArrayList<>();
192
193
if (principal != null) {
194
sql.append(" AND principal = ?");
195
params.add(principal);
196
}
197
if (after != null) {
198
sql.append(" AND timestamp > ?");
199
params.add(Timestamp.from(after));
200
}
201
if (type != null) {
202
sql.append(" AND type = ?");
203
params.add(type);
204
}
205
206
return jdbcTemplate.query(sql.toString(), params.toArray(), this::mapRowToAuditEvent);
207
}
208
}
209
```
210
211
### Audit Event Listeners
212
213
Listeners for handling audit events and Spring application events.
214
215
```java { .api }
216
/**
217
* Base class for audit listeners
218
*/
219
public abstract class AbstractAuditListener {
220
221
private AuditEventRepository auditEventRepository;
222
223
public AbstractAuditListener(AuditEventRepository auditEventRepository);
224
225
protected void publish(AuditEvent event);
226
227
protected AuditEventRepository getAuditEventRepository();
228
}
229
230
/**
231
* Spring application event for auditing
232
*/
233
public class AuditApplicationEvent extends ApplicationEvent {
234
235
public AuditApplicationEvent(Object source, AuditEvent auditEvent);
236
public AuditApplicationEvent(Object source, String principal, String type, Object... data);
237
238
public AuditEvent getAuditEvent();
239
}
240
241
/**
242
* Default audit event listener
243
*/
244
public class AuditListener extends AbstractAuditListener {
245
246
public AuditListener(AuditEventRepository auditEventRepository);
247
248
@EventListener
249
public void onAuditEvent(AuditApplicationEvent event);
250
}
251
```
252
253
**Usage Example:**
254
255
```java
256
@Component
257
public class SecurityAuditListener extends AbstractAuditListener {
258
259
public SecurityAuditListener(AuditEventRepository auditEventRepository) {
260
super(auditEventRepository);
261
}
262
263
@EventListener
264
public void onAuthenticationSuccess(AuthenticationSuccessEvent event) {
265
String principal = event.getAuthentication().getName();
266
AuditEvent auditEvent = new AuditEvent(
267
principal,
268
"AUTHENTICATION_SUCCESS",
269
"type", event.getAuthentication().getClass().getSimpleName(),
270
"authorities", event.getAuthentication().getAuthorities().toString()
271
);
272
publish(auditEvent);
273
}
274
275
@EventListener
276
public void onAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
277
String principal = event.getAuthentication().getName();
278
AuditEvent auditEvent = new AuditEvent(
279
principal,
280
"AUTHENTICATION_FAILURE",
281
"type", event.getException().getClass().getSimpleName(),
282
"message", event.getException().getMessage()
283
);
284
publish(auditEvent);
285
}
286
287
@EventListener
288
public void onAuthorizationFailure(AuthorizationFailureEvent event) {
289
String principal = event.getAuthentication().getName();
290
AuditEvent auditEvent = new AuditEvent(
291
principal,
292
"AUTHORIZATION_FAILURE",
293
"resource", event.getResource().toString(),
294
"reason", event.getAccessDeniedException().getMessage()
295
);
296
publish(auditEvent);
297
}
298
}
299
300
@Service
301
public class BusinessAuditService {
302
303
private final ApplicationEventPublisher eventPublisher;
304
305
public BusinessAuditService(ApplicationEventPublisher eventPublisher) {
306
this.eventPublisher = eventPublisher;
307
}
308
309
public void auditBusinessOperation(String principal, String operation, Object... data) {
310
AuditApplicationEvent auditEvent = new AuditApplicationEvent(
311
this, principal, operation, data
312
);
313
eventPublisher.publishEvent(auditEvent);
314
}
315
316
public void auditDataAccess(String principal, String resource, String action) {
317
auditBusinessOperation(principal, "DATA_ACCESS",
318
"resource", resource,
319
"action", action,
320
"timestamp", Instant.now()
321
);
322
}
323
}
324
```
325
326
### Audit Events Endpoint
327
328
Built-in endpoint for exposing audit events.
329
330
```java { .api }
331
/**
332
* Endpoint to expose audit events
333
*/
334
@Endpoint(id = "auditevents")
335
public class AuditEventsEndpoint {
336
337
public AuditEventsEndpoint(AuditEventRepository repository);
338
339
/**
340
* Get audit events with optional filtering
341
*/
342
@ReadOperation
343
public AuditEventsDescriptor eventsWithPrincipalAndType(@Nullable String principal, @Nullable String type);
344
345
/**
346
* Get audit events after a specific date
347
*/
348
@ReadOperation
349
public AuditEventsDescriptor eventsWithPrincipalAndTypeAndDate(@Nullable String principal,
350
@Nullable String type,
351
@Nullable String after);
352
353
/**
354
* Descriptor for audit events response
355
*/
356
public static final class AuditEventsDescriptor {
357
private final List<AuditEvent> events;
358
359
public AuditEventsDescriptor(List<AuditEvent> events);
360
361
public List<AuditEvent> getEvents();
362
}
363
}
364
```
365
366
**Usage Example:**
367
368
```java
369
@RestController
370
public class CustomAuditController {
371
372
private final AuditEventRepository auditEventRepository;
373
374
public CustomAuditController(AuditEventRepository auditEventRepository) {
375
this.auditEventRepository = auditEventRepository;
376
}
377
378
@GetMapping("/admin/audit/users/{username}")
379
public ResponseEntity<List<AuditEvent>> getUserAuditEvents(@PathVariable String username) {
380
List<AuditEvent> events = auditEventRepository.find(username, null, null);
381
return ResponseEntity.ok(events);
382
}
383
384
@GetMapping("/admin/audit/failures")
385
public ResponseEntity<List<AuditEvent>> getFailureEvents() {
386
Instant oneWeekAgo = Instant.now().minus(7, ChronoUnit.DAYS);
387
List<AuditEvent> failures = auditEventRepository.find(null, oneWeekAgo, "AUTHENTICATION_FAILURE");
388
return ResponseEntity.ok(failures);
389
}
390
391
@PostMapping("/admin/audit/custom")
392
public ResponseEntity<Void> createCustomAuditEvent(@RequestBody CustomAuditRequest request) {
393
AuditEvent event = new AuditEvent(
394
request.getPrincipal(),
395
request.getType(),
396
request.getData()
397
);
398
auditEventRepository.add(event);
399
return ResponseEntity.ok().build();
400
}
401
}
402
```
403
404
### Common Audit Event Types
405
406
Standard audit event types commonly used in Spring Boot applications.
407
408
```java { .api }
409
// Common audit event type constants (typically defined as string constants)
410
411
/**
412
* Authentication-related audit events
413
*/
414
public final class AuthenticationAuditEventTypes {
415
public static final String AUTHENTICATION_SUCCESS = "AUTHENTICATION_SUCCESS";
416
public static final String AUTHENTICATION_FAILURE = "AUTHENTICATION_FAILURE";
417
public static final String AUTHENTICATION_SWITCH = "AUTHENTICATION_SWITCH";
418
public static final String LOGOUT = "LOGOUT";
419
}
420
421
/**
422
* Authorization-related audit events
423
*/
424
public final class AuthorizationAuditEventTypes {
425
public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
426
public static final String ACCESS_GRANTED = "ACCESS_GRANTED";
427
public static final String ACCESS_DENIED = "ACCESS_DENIED";
428
}
429
430
/**
431
* Session-related audit events
432
*/
433
public final class SessionAuditEventTypes {
434
public static final String SESSION_CREATED = "SESSION_CREATED";
435
public static final String SESSION_DESTROYED = "SESSION_DESTROYED";
436
public static final String SESSION_EXPIRED = "SESSION_EXPIRED";
437
}
438
```
439
440
**Usage Example:**
441
442
```java
443
@Component
444
public class ComprehensiveAuditEventPublisher {
445
446
private final ApplicationEventPublisher eventPublisher;
447
448
public ComprehensiveAuditEventPublisher(ApplicationEventPublisher eventPublisher) {
449
this.eventPublisher = eventPublisher;
450
}
451
452
public void publishAuthenticationSuccess(String username, String source) {
453
publishAuditEvent(username, AuthenticationAuditEventTypes.AUTHENTICATION_SUCCESS,
454
"source", source,
455
"timestamp", Instant.now()
456
);
457
}
458
459
public void publishAuthenticationFailure(String username, String reason) {
460
publishAuditEvent(username, AuthenticationAuditEventTypes.AUTHENTICATION_FAILURE,
461
"reason", reason,
462
"timestamp", Instant.now()
463
);
464
}
465
466
public void publishAuthorizationFailure(String username, String resource) {
467
publishAuditEvent(username, AuthorizationAuditEventTypes.AUTHORIZATION_FAILURE,
468
"resource", resource,
469
"timestamp", Instant.now()
470
);
471
}
472
473
private void publishAuditEvent(String principal, String type, Object... data) {
474
AuditApplicationEvent event = new AuditApplicationEvent(this, principal, type, data);
475
eventPublisher.publishEvent(event);
476
}
477
}
478
```