知識庫 / Spring / Spring Boot RSS 訂閱

Spring Boot 身份驗證審計支持

Spring Boot
HongKong
4
02:39 PM · Dec 06 ,2025

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>3.1.5</version>
</dependency>

最新版本可在 Maven Central 倉庫中獲取。

3. 監聽身份驗證和授權事件

要記錄 Spring Boot 應用程序中的所有身份驗證和授權嘗試,只需定義一個帶有監聽方法(listener method)的 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 Bean 都可以使用;新的 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

在本示例中,監聽器接收到了三個 AuditApplicationEvent

  1. 未登錄狀態下,已請求訪問受限頁面
  2. 使用錯誤的密碼登錄
  3. 第二次使用正確的密碼

4. 認證審計監聽器

如果 Spring Boot 中 AuthorizationAuditListener 暴露的信息不足,您可以 創建自己的 Bean 以暴露更多信息。

下面我們來看一個示例,其中我們還暴露了授權失敗時訪問的請求 URL:

@Component 
public class ExposeAttemptedPathAuthorizationAuditListener extends AbstractAuthorizationAuditListener {

    public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";

    @Override
    public void onApplicationEvent(AuthorizationEvent event) {
        if (event instanceof AuthorizationDeniedEvent) {
            onAuthorizationFailureEvent(event);
        }
    }

    private void onAuthorizationFailureEvent(AuthorizationEvent event) {
        String name = this.getName(event.getAuthentication());
        Map<String, Object> data = new LinkedHashMap<>();
        Object details = this.getDetails(event.getAuthentication());
        if (details != null) {
            data.put("details", details);
        }
        publish(new AuditEvent(name, "AUTHORIZATION_FAILURE", data));
    }

    private String getName(Supplier authentication) {
        try {
            return authentication.get().getName();
        } catch (Exception exception) {
            return "";
        }
    }

    private Object getDetails(Supplier authentication) {
        try {
            return (authentication.get()).getDetails();
        } catch (Exception exception) {
            return null;
        }
    }
}

我們現在可以記錄請求 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,因此我們可以使用該基類中的 publish 方法在我們的實現中。

如果想要測試它,請查看源代碼並運行:

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 中的 actuator 支持,可以輕鬆地記錄來自用户的認證和授權嘗試。讀者還可以參考 生產級審計 以獲取更多信息。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.