Suporte para auditoria de autenticação de inicialização Spring

Suporte para auditoria de autenticação de inicialização Spring

1. Visão geral

Neste breve artigo, exploraremos o módulo Spring Boot Actuator e o suporte para publicar eventos de autenticação e autorização em conjunto com Spring Security.

2. Dependências do Maven

Primeiro, precisamos adicionarspring-boot-starter-actuator ao nossopom.xml:


    org.springframework.boot
    spring-boot-starter-actuator
    2.1.7.RELEASE

A versão mais recente está disponível no repositórioMaven Central.

3. Ouvindo eventos de autenticação e autorização

Para registrar todas as tentativas de autenticação e autorização em um aplicativo Spring Boot, podemos apenas definir um bean com um método listener:

@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());
    }
}

Observe que estamos apenas gerando algumas das coisas que estão disponíveis emAuditApplicationEvent para mostrar quais informações estão disponíveis. Em um aplicativo real, convém armazenar essas informações em um repositório ou cache para processá-las ainda mais.

Observe que qualquer bean Spring funcionará; os princípios básicos do novo suporte a eventos da Primavera são bastante simples:

  • anote o método com@EventListener

  • adicioneAuditApplicationEvent como o único argumento do método

A saída da execução do aplicativo será parecida com esta:

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

Neste exemplo, trêsAuditApplicationEvents foram recebidos pelo ouvinte:

  1. Sem fazer logon, o acesso foi solicitado a uma página restrita

  2. Uma senha incorreta foi usada durante o logon

  3. Uma senha correta foi usada na segunda vez

4. Um ouvinte de auditoria de autenticação

Se a informação exposta porAuthorizationAuditListener do Spring Boot não for suficiente, você podecreate your own bean to expose more information.

Vejamos um exemplo, onde também expomos o URL de solicitação que foi acessado quando a autorização falhou:

@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 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));
    }
}

Agora podemos registrar o URL da solicitação em nosso ouvinte:

@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"));
    }
}

Como resultado, a saída agora contém o URL solicitado:

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

Observe que estendemos a partir doAbstractAuthorizationAuditListener abstrato neste exemplo, então podemos usar o métodopublish dessa classe base em nossa implementação.

Se você quiser testá-lo, verifique o código-fonte e execute:

mvn clean spring-boot:run

Depois disso, você pode apontar seu navegador parahttp://localhost:8080/.

5. Armazenamento de eventos de auditoria

Por padrão, o Spring Boot armazena os eventos de auditoria em umAuditEventRepository. Se você não criar um bean com uma implementação própria, umInMemoryAuditEventRepository será instalado para você.

OInMemoryAuditEventRepository é um tipo de buffer circular que armazena os últimos 4000 eventos de auditoria na memória. Esses eventos podem ser acessados ​​por meio do endpoint de gerenciamentohttp://localhost:8080/auditevents.

Isso retorna uma representação JSON dos eventos de auditoria:

{
  "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. Conclusão

Com o suporte do atuador no Spring Boot, torna-se trivial registrar as tentativas de autenticação e autorização dos usuários. O leitor também é referido aproduction ready auditing para algumas informações adicionais.

O código deste artigo pode ser encontradoover on GitHub.