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:
- 未登錄狀態下,已請求訪問受限頁面
- 使用錯誤的密碼登錄
- 第二次使用正確的密碼
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 支持,可以輕鬆地記錄來自用户的認證和授權嘗試。讀者還可以參考 生產級審計 以獲取更多信息。