Статьи

Аудит инфраструктуры для вашего приложения с использованием Spring AOP, пользовательских аннотаций и отражений

В следующем посте будет показано, как написать простой аудит с использованием Spring AOP и аннотаций. Механизм аудита будет чистым, эффективным и простым в обслуживании (и Kewwl!).

Я продемонстрирую свой пример на системе управления пользователями (я полагаю, у вас есть общие знания по рефлексии и АОП).

Мы начнем с простой таблицы БД для хранения наших данных аудита:

1
2
3
4
5
6
7
`id`,
 `username`
 `user_type`
 `action`
 `target_user`
 `date`
 `user_ip`

Нам нужно заполнить 4 основных поля (Username, UserType, Action, TargetUser)

* Имя пользователя — пользователь, который выполняет действие

* TargetUser — Target user действие выполняется.

Теперь давайте создадим новую аннотацию, чтобы отметить наш метод wanna-be-аудит. Мы собираемся быть очень «креативными» и использовать: @AuditAble

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Auditable {
AuditingActionType actionType();
}

Пример аннотированного метода @AuditAble:

1
2
3
4
5
6
7
@Override
 @Transactional
 @Auditable(actionType = AuditingActionType.INTERNAL_USER_REGISTRATION)
 public void createInternalUser(UserDTO userDTO) {
 userCreationService.createInternalUserOnDB(userDTO);
 
}

Наш будущий аспект (aop) будет собирать некоторые данные аудита из параметров метода с использованием DTO. В нашем случае целевое имя пользователя и actionType будут собраны в качестве нашей аудиторской информации.

Для этого я создал еще одну аннотацию AuditingTargetUsername :

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface AuditingTargetUsername {
String value() default "";
}

Итак, внутри UserDTO мы получили:

1
2
3
4
5
6
7
8
9
public abstract class UserDTO implements Serializable {
 
 @NotNull
 @AuditingTargetUsername
 private String userName;
 
...
 
}

Мы аннотировали userName с помощью @AuditingTargetUsername. Эта информация будет собрана позже.

Теперь давайте создадим аспект нашего АОП. Здесь вся логика аудита собрана и выполнена (перехват методов @Auditable, извлечение информации из аннотаций, использование репозитория для сохранения окончательной записи аудита):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
public class AuditingAspect {
 
....
 
@After("@annotation(auditable)")
 @Transactional
 public void logAuditActivity(JoinPoint jp, Auditable auditable) {
 String targetAuditingUser;
 String actionType = auditable.actionType().getDescription();
 
 String auditingUsername = Authentication auth = SecurityContextHolder.getContext().getAuthentication().getName()
 role = userService.getCurrentUser(false).getPermissionsList().toString();
 auditingUsernameIp = request.getRemoteAddr();
 }
 logger.info(
 "Auditing information. auditingUsername=" + auditingUsername + ", actionType=" + actionType + ", role=" + role + ", targetAuditingUser="
 + targetAuditingUser + " auditingUsernameIp=" + auditingUsernameIp
 );
 auditingRepository
 .save(new AuditingEntity(auditingUsername, role, actionType, targetAuditingUser, auditingUsernameIp,
 new Timestamp(new java.util.Date().getTime())));
 }

плохо объясняет основные области кода:

Pointcut — все @ Auditable.annotations
Совет — введите @After (мы хотим провести аудит после вызова метода)

Значение ActionType извлекается через объявление аннотированного метода:

@Auditable ( actionType = AuditingActionType.INTERNAL_USER_REGISTRATION )

auditingUsername — текущий пользователь, который выполняет действие (в нашем случае зарегистрированный пользователь). Я получил это через SecurityContext (Spring Security).

Теперь мы извлечем поле @targetAuditingUser через отражение во время выполнения:

01
02
03
04
05
06
07
08
09
10
targetAuditingUser = extractTargetAuditingUser(jp.getArgs());
...
 
public String extractTargetAuditingUserFromAnnotation(Object obj) {
...
 result = getTargetAuditingUserViaAnnotation(obj);
 
...
 
}

Вот логика для извлечения аннотированных полей с помощью отражения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
private String getTargetAuditingUserViaAnnotation(Object obj) {
class cl=obj.getClass()
 String result = null;
 try {
 for (Field f : cl.getDeclaredFields())
 for (Annotation a : f.getAnnotations()) {
 if (a.annotationType() == AuditingTargetUsername.class) {
 f.setAccessible(true);
 Field annotatedFieldName = cl.getDeclaredField(f.getName());
 annotatedFieldName.setAccessible(true);
 String annotatedFieldVal = (String) annotatedFieldName.get(obj);
 logger.debug("Found auditing annotation. type=" + a.annotationType() + " value=" + annotatedFieldVal.toString());
 result = annotatedFieldVal;
 }
 }
 } catch (Exception e) {
 logger.error("Error extracting auditing annotations from obj" + obj.getClass());
 }
 return result;
 }

Результат на БД:

audit1

Вот и все. У нас есть чистая инфраструктура аудита, все, что вам нужно, это аннотировать ваш метод с помощью @Auditable и комментировать внутри ваших DTO / Entities необходимую информацию для аудита.

Идан.