«1. Обзор

В этой короткой статье мы рассмотрим модуль Spring Boot Actuator и поддержку публикации событий аутентификации и авторизации в сочетании с Spring Security.

2. Зависимости Maven

Во-первых, нам нужно добавить spring-boot-starter-actuator в наш pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

Последняя версия доступна в центральном репозитории Maven.

3. Прослушивание событий аутентификации и авторизации

Чтобы регистрировать все попытки аутентификации и авторизации в приложении Spring Boot, мы можем просто определить bean-компонент с методом прослушивателя:

@Component
public class LoginAttemptsLogger {

    @EventListener
    public void auditEventHappened(
      AuditApplicationEvent auditApplicationEvent) {
        
        AuditEvent auditEvent = auditApplicationEvent.getAuditEvent();
        System.out.println("Principal " + auditEvent.getPrincipal() 
          + " - " + auditEvent.getType());

        WebAuthenticationDetails details = 
          (WebAuthenticationDetails) auditEvent.getData().get("details");
        System.out.println("Remote IP address: " 
          + details.getRemoteAddress());
        System.out.println("  Session Id: " + details.getSessionId());
    }
}

Обратите внимание, что мы просто вывод некоторых вещей, доступных в AuditApplicationEvent, чтобы показать, какая информация доступна. В реальном приложении вы можете сохранить эту информацию в репозитории или кэше для дальнейшей обработки.

Обратите внимание, что любой компонент Spring будет работать; основы новой поддержки событий Spring довольно просты:

    аннотируйте метод с помощью @EventListener добавьте AuditApplicationEvent в качестве единственного аргумента метода

Результат запуска приложения будет выглядеть примерно так:

Principal anonymousUser - AUTHORIZATION_FAILURE
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: null
Principal user - AUTHENTICATION_FAILURE
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: BD41692232875A5A65C5E35E63D784F6
Principal user - AUTHENTICATION_SUCCESS
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: BD41692232875A5A65C5E35E63D784F6

В этом примере прослушиватель получил три события AuditApplicationEvents:

  1. Without logging on, access has been requested to a restricted page
  2. A wrong password has been used while logging on
  3. A correct password has been used the second time around

4. Слушатель аудита аутентификации

Если информации, предоставленной прослушивателем AuthorizationAuditListener Spring Boot, недостаточно, вы можете создать свой собственный bean-компонент для предоставления дополнительной информации. .

Давайте рассмотрим пример, в котором мы также раскрываем URL-адрес запроса, к которому обращались при сбое авторизации:

@Component
public class ExposeAttemptedPathAuthorizationAuditListener 
  extends AbstractAuthorizationAuditListener {

    public static final String AUTHORIZATION_FAILURE 
      = "AUTHORIZATION_FAILURE";

    @Override
    public void onApplicationEvent(AbstractAuthorizationEvent event) {
        if (event instanceof AuthorizationFailureEvent) {
            onAuthorizationFailureEvent((AuthorizationFailureEvent) event);
        }
    }

    private void onAuthorizationFailureEvent(
      AuthorizationFailureEvent event) {
        Map<String, Object> data = new HashMap<>();
        data.put(
          "type", event.getAccessDeniedException().getClass().getName());
        data.put("message", event.getAccessDeniedException().getMessage());
        data.put(
          "requestUrl", ((FilterInvocation)event.getSource()).getRequestUrl() );
        
        if (event.getAuthentication().getDetails() != null) {
            data.put("details", 
              event.getAuthentication().getDetails());
        }
        publish(new AuditEvent(event.getAuthentication().getName(), 
          AUTHORIZATION_FAILURE, data));
    }
}

Теперь мы можем зарегистрировать URL-адрес запроса в нашем слушателе:

@Component
public class LoginAttemptsLogger {

    @EventListener
    public void auditEventHappened(
      AuditApplicationEvent auditApplicationEvent) {
        AuditEvent auditEvent = auditApplicationEvent.getAuditEvent();
 
        System.out.println("Principal " + auditEvent.getPrincipal() 
          + " - " + auditEvent.getType());

        WebAuthenticationDetails details
          = (WebAuthenticationDetails) auditEvent.getData().get("details");
 
        System.out.println("  Remote IP address: " 
          + details.getRemoteAddress());
        System.out.println("  Session Id: " + details.getSessionId());
        System.out.println("  Request URL: " 
          + auditEvent.getData().get("requestUrl"));
    }
}

Как В результате выходные данные теперь содержат запрошенный URL-адрес:

Principal anonymousUser - AUTHORIZATION_FAILURE
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: null
  Request URL: /hello

Обратите внимание, что в этом примере мы расширились от абстрактного AbstractAuthorizationAuditListener, поэтому мы можем использовать метод публикации из этого базового класса в нашей реализации.

Если вы хотите протестировать его, проверьте исходный код и запустите:

mvn clean spring-boot:run

После этого вы можете указать в своем браузере http://localhost:8080/.

5. Хранение событий аудита

По умолчанию Spring Boot сохраняет события аудита в AuditEventRepository. Если вы не создадите bean-компонент с собственной реализацией, вам будет подключен InMemoryAuditEventRepository.

InMemoryAuditEventRepository — это своего рода кольцевой буфер, в котором в памяти хранятся последние 4000 событий аудита. Затем к этим событиям можно получить доступ через конечную точку управления http://localhost:8080/auditevents.

Это возвращает JSON-представление событий аудита:

{
  "events": [
    {
      "timestamp": "2017-03-09T19:21:59+0000",
      "principal": "anonymousUser",
      "type": "AUTHORIZATION_FAILURE",
      "data": {
        "requestUrl": "/auditevents",
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": null
        },
        "type": "org.springframework.security.access.AccessDeniedException",
        "message": "Access is denied"
      }
    },
    {
      "timestamp": "2017-03-09T19:22:00+0000",
      "principal": "anonymousUser",
      "type": "AUTHORIZATION_FAILURE",
      "data": {
        "requestUrl": "/favicon.ico",
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": "18FA15865F80760521BBB736D3036901"
        },
        "type": "org.springframework.security.access.AccessDeniedException",
        "message": "Access is denied"
      }
    },
    {
      "timestamp": "2017-03-09T19:22:03+0000",
      "principal": "user",
      "type": "AUTHENTICATION_SUCCESS",
      "data": {
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": "18FA15865F80760521BBB736D3036901"
        }
      }
    }
  ]
}

6. Заключение

Благодаря поддержке привода в Spring Boot регистрация попыток аутентификации и авторизации пользователей становится тривиальной. Читатель также отсылается к аудиту готовности к производству для получения дополнительной информации.

Код из этой статьи можно найти на GitHub.