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
这三种典型场景:
- 未登录用户访问受限资源
- 用户使用错误密码登录
- 用户成功登录
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
5. 存储审计事件
Spring Boot 默认使用 AuditEventRepository
存储事件。未自定义时,会自动装配 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 让认证审计变得异常简单。只需少量配置就能:
- ✅ 捕获所有认证/授权事件
- ✅ 获取 IP、会话 ID 等关键信息
- ✅ 自定义监听器扩展审计维度
- ✅ 通过管理接口查询历史事件
生产环境建议结合持久化存储(如数据库),避免内存存储的局限性。更多细节可参考官方生产审计指南。