1. 概述

本文将探讨 Spring Boot Actuator 模块如何与 Spring Security 协作,实现认证和授权事件的审计功能。通过简单的配置,我们就能轻松捕获用户登录行为和权限校验过程,这对安全审计和问题排查特别有用。

2. Maven 依赖

首先在 pom.xml 中添加 Actuator 依赖:

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

最新版本可在 Maven Central 获取。

3. 监听认证和授权事件

要记录所有认证和授权尝试,只需定义一个带监听方法的 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());
    }
}

✅ 关键点:

  • 使用 @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

这三种典型场景:

  1. 未登录用户访问受限资源
  2. 用户使用错误密码登录
  3. 用户成功登录

4. 自定义认证审计监听器

当默认的 AuthorizationAuditListener 信息不足时,可以自定义监听器暴露更多细节。比如捕获授权失败时的请求 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"));
    }
}

输出结果:

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 存储事件。未自定义时,会自动装配 InMemoryAuditEventRepository

返回的 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 让认证审计变得异常简单。只需少量配置就能:

  • ✅ 捕获所有认证/授权事件
  • ✅ 获取 IP、会话 ID 等关键信息
  • ✅ 自定义监听器扩展审计维度
  • ✅ 通过管理接口查询历史事件

生产环境建议结合持久化存储(如数据库),避免内存存储的局限性。更多细节可参考官方生产审计指南


原始标题:Spring Boot Authentication Auditing Support | Baeldung