|
| 1 | +[[auditing]] |
| 2 | += Auditing |
| 3 | + |
| 4 | +[[auditing.basics]] |
| 5 | +== Basics |
| 6 | +Spring Data provides sophisticated support to transparently keep track of who created or changed an entity and when the change happened.To benefit from that functionality, you have to equip your entity classes with auditing metadata that can be defined either using annotations or by implementing an interface. |
| 7 | +Additionally, auditing has to be enabled either through Annotation configuration or XML configuration to register the required infrastructure components. |
| 8 | +Please refer to the store-specific section for configuration samples. |
| 9 | + |
| 10 | +[NOTE] |
| 11 | +==== |
| 12 | +Applications that only track creation and modification dates are not required do make their entities implement <<auditing.auditor-aware, `AuditorAware`>>. |
| 13 | +==== |
| 14 | + |
| 15 | +[[auditing.annotations]] |
| 16 | +=== Annotation-based Auditing Metadata |
| 17 | +We provide `@CreatedBy` and `@LastModifiedBy` to capture the user who created or modified the entity as well as `@CreatedDate` and `@LastModifiedDate` to capture when the change happened. |
| 18 | + |
| 19 | +.An audited entity |
| 20 | +==== |
| 21 | +[source,java] |
| 22 | +---- |
| 23 | +class Customer { |
| 24 | +
|
| 25 | + @CreatedBy |
| 26 | + private User user; |
| 27 | +
|
| 28 | + @CreatedDate |
| 29 | + private Instant createdDate; |
| 30 | +
|
| 31 | + // … further properties omitted |
| 32 | +} |
| 33 | +---- |
| 34 | +==== |
| 35 | + |
| 36 | +As you can see, the annotations can be applied selectively, depending on which information you want to capture. |
| 37 | +The annotations, indicating to capture when changes are made, can be used on properties of type JDK8 date and time types, `long`, `Long`, and legacy Java `Date` and `Calendar`. |
| 38 | + |
| 39 | +Auditing metadata does not necessarily need to live in the root level entity but can be added to an embedded one (depending on the actual store in use), as shown in the snippet below. |
| 40 | + |
| 41 | +.Audit metadata in embedded entity |
| 42 | +==== |
| 43 | +[source,java] |
| 44 | +---- |
| 45 | +class Customer { |
| 46 | +
|
| 47 | + private AuditMetadata auditingMetadata; |
| 48 | +
|
| 49 | + // … further properties omitted |
| 50 | +} |
| 51 | +
|
| 52 | +class AuditMetadata { |
| 53 | +
|
| 54 | + @CreatedBy |
| 55 | + private User user; |
| 56 | +
|
| 57 | + @CreatedDate |
| 58 | + private Instant createdDate; |
| 59 | +
|
| 60 | +} |
| 61 | +---- |
| 62 | +==== |
| 63 | + |
| 64 | +[[auditing.interfaces]] |
| 65 | +=== Interface-based Auditing Metadata |
| 66 | +In case you do not want to use annotations to define auditing metadata, you can let your domain class implement the `Auditable` interface. It exposes setter methods for all of the auditing properties. |
| 67 | + |
| 68 | +[[auditing.auditor-aware]] |
| 69 | +=== `AuditorAware` |
| 70 | + |
| 71 | +In case you use either `@CreatedBy` or `@LastModifiedBy`, the auditing infrastructure somehow needs to become aware of the current principal. To do so, we provide an `AuditorAware<T>` SPI interface that you have to implement to tell the infrastructure who the current user or system interacting with the application is. The generic type `T` defines what type the properties annotated with `@CreatedBy` or `@LastModifiedBy` have to be. |
| 72 | + |
| 73 | +The following example shows an implementation of the interface that uses Spring Security's `Authentication` object: |
| 74 | + |
| 75 | +.Implementation of `AuditorAware` based on Spring Security |
| 76 | +==== |
| 77 | +[source, java] |
| 78 | +---- |
| 79 | +class SpringSecurityAuditorAware implements AuditorAware<User> { |
| 80 | +
|
| 81 | + @Override |
| 82 | + public Optional<User> getCurrentAuditor() { |
| 83 | +
|
| 84 | + return Optional.ofNullable(SecurityContextHolder.getContext()) |
| 85 | + .map(SecurityContext::getAuthentication) |
| 86 | + .filter(Authentication::isAuthenticated) |
| 87 | + .map(Authentication::getPrincipal) |
| 88 | + .map(User.class::cast); |
| 89 | + } |
| 90 | +} |
| 91 | +---- |
| 92 | +==== |
| 93 | + |
| 94 | +The implementation accesses the `Authentication` object provided by Spring Security and looks up the custom `UserDetails` instance that you have created in your `UserDetailsService` implementation. We assume here that you are exposing the domain user through the `UserDetails` implementation but that, based on the `Authentication` found, you could also look it up from anywhere. |
| 95 | + |
| 96 | +[[auditing.reactive-auditor-aware]] |
| 97 | +=== `ReactiveAuditorAware` |
| 98 | + |
| 99 | +When using reactive infrastructure you might want to make use of contextual information to provide `@CreatedBy` or `@LastModifiedBy` information. |
| 100 | +We provide an `ReactiveAuditorAware<T>` SPI interface that you have to implement to tell the infrastructure who the current user or system interacting with the application is. The generic type `T` defines what type the properties annotated with `@CreatedBy` or `@LastModifiedBy` have to be. |
| 101 | + |
| 102 | +The following example shows an implementation of the interface that uses reactive Spring Security's `Authentication` object: |
| 103 | + |
| 104 | +.Implementation of `ReactiveAuditorAware` based on Spring Security |
| 105 | +==== |
| 106 | +[source, java] |
| 107 | +---- |
| 108 | +class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> { |
| 109 | +
|
| 110 | + @Override |
| 111 | + public Mono<User> getCurrentAuditor() { |
| 112 | +
|
| 113 | + return ReactiveSecurityContextHolder.getContext() |
| 114 | + .map(SecurityContext::getAuthentication) |
| 115 | + .filter(Authentication::isAuthenticated) |
| 116 | + .map(Authentication::getPrincipal) |
| 117 | + .map(User.class::cast); |
| 118 | + } |
| 119 | +} |
| 120 | +---- |
| 121 | +==== |
| 122 | + |
| 123 | +The implementation accesses the `Authentication` object provided by Spring Security and looks up the custom `UserDetails` instance that you have created in your `UserDetailsService` implementation. We assume here that you are exposing the domain user through the `UserDetails` implementation but that, based on the `Authentication` found, you could also look it up from anywhere. |
0 commit comments