Springboot Actuator之十:actuator中的audit包

前言
這篇文章咱們來分析一下org.springframework.boot.actuate.security,org.springframework.boot.actuate.audit中的代碼,這2個包的類是對spring security 的事件進行處理的.類圖以下:html

 

2、源碼解析

2.一、AuditEvent事件類

AuditEvent–> 1個值對象–>表明了1個audit event: 在特定的時間,1個特定的用戶或者代理,實施了1個特定類型的動做.AuditEvent記錄了有關AuditEvent的細節.web

其類上有以下註解:spring

@JsonInclude(Include.NON_EMPTY)

表明該類中爲空(「」)或者爲null的屬性不會被序列化。數組

該類的字段以下:session

private final Date timestamp;

// 資源
private final String principal;

private final String type;

private final Map<String, Object> data;

 

2.二、AuditApplicationEvent事件類

AuditApplicationEvent–> 封裝AuditEvent.代碼以下:app

public class AuditApplicationEvent extends ApplicationEvent {

    private final AuditEvent auditEvent;


    public AuditApplicationEvent(String principal, String type,
            Map<String, Object> data) {
        this(new AuditEvent(principal, type, data));
    }

    AuditApplicationEvent(String principal, String type, String... data) {
        this(new AuditEvent(principal, type, data));
    }


    public AuditApplicationEvent(Date timestamp, String principal, String type,
            Map<String, Object> data) {
        this(new AuditEvent(timestamp, principal, type, data));
    }


    public AuditApplicationEvent(AuditEvent auditEvent) {
        super(auditEvent);
        Assert.notNull(auditEvent, "AuditEvent must not be null");
        this.auditEvent = auditEvent;
    }


    public AuditEvent getAuditEvent() {
        return this.auditEvent;
    }

}

 

2.三、AbstractAuditListener

AbstractAuditListener –>處理AuditApplicationEvent事件的抽象類.代碼以下:ide

public abstract class AbstractAuditListener
        implements ApplicationListener<AuditApplicationEvent> {

    @Override
    public void onApplicationEvent(AuditApplicationEvent event) {
        onAuditEvent(event.getAuditEvent());
    }

    protected abstract void onAuditEvent(AuditEvent event);

}

 

2.四、AuditEventRepository

AuditEventRepository–> 關於AuditEvent的dao實現.聲明瞭以下4個方法:spring-boot

// 添加日誌
void add(AuditEvent event);

// 查詢指定日期以後的AuditEvent
List<AuditEvent> find(Date after);

// 根據給定的Date和principal(資源)得到對應的AuditEvent
List<AuditEvent> find(String principal, Date after);

// 根據給的date,principal,type 類獲取給定的AuditEvent
List<AuditEvent> find(String principal, Date after, String type);

 

2.五、InMemoryAuditEventRepository

InMemoryAuditEventRepository –> AuditEventRepository接口的惟一實現.post

該類的字段以下:性能

// AuditEvent數組默認的默認大小
private static final int DEFAULT_CAPACITY = 4000;

// 用於對events進行操做時 加的鎖
private final Object monitor = new Object();

/**
* Circular buffer of the event with tail pointing to the last element.
* 循環數組
*/
private AuditEvent[] events;

// 最後1個元素的下標
private volatile int tail = -1;

構造器以下:

public InMemoryAuditEventRepository() {
   this(DEFAULT_CAPACITY);
}

public InMemoryAuditEventRepository(int capacity) {
   this.events = new AuditEvent[capacity];
}

AuditEventRepository中的方法實現以下:

    @Override
    public void add(AuditEvent event) {
        Assert.notNull(event, "AuditEvent must not be null");
        synchronized (this.monitor) {
            this.tail = (this.tail + 1) % this.events.length;
            this.events[this.tail] = event;
        }
    }

    @Override
    public List<AuditEvent> find(Date after) {
        return find(null, after, null);
    }

    @Override
    public List<AuditEvent> find(String principal, Date after) {
        return find(principal, after, null);
    }
    //上面兩個方法最終調用這個方法
    @Override
    public List<AuditEvent> find(String principal, Date after, String type) {
        LinkedList<AuditEvent> events = new LinkedList<AuditEvent>();
        synchronized (this.monitor) {
        // 1. 遍歷events
        for (int i = 0; i < this.events.length; i++) {
            // 1.1 得到最新的AuditEvent
            AuditEvent event = resolveTailEvent(i);
            // 1.2 若是AuditEvent 不等於null而且符合查詢要求的話,就加入到events中
            if (event != null && isMatch(principal, after, type, event)) {
                events.addFirst(event);
            }
        }
    }
        // 2. 返回結果集
        return events;
    }

    //過濾不和條件的事件
    private boolean isMatch(String principal, Date after, String type, AuditEvent event) {
        boolean match = true;
        match = match && (principal == null || event.getPrincipal().equals(principal));
        match = match && (after == null || event.getTimestamp().compareTo(after) >= 0);
        match = match && (type == null || event.getType().equals(type));
        return match;
    }
    
    //得到最新的AuditEvent
    private AuditEvent resolveTailEvent(int offset) {
        int index = ((this.tail + this.events.length - offset) % this.events.length);
        return this.events[index];
    }

 

返回結果集

這裏有2個問題:

  一、前面說過訪問events的時候都須要進行加鎖,爲何resolveTailEvent方法沒有加鎖?

    緣由以下: resolveTailEvent的調用點只有1個,就是在find(String Date , String)中,而在該方法中已經加鎖了,所以該方法不須要加鎖.

  二、resolveTailEvent方法加鎖能夠嗎

    答: 能夠,緣由是synchronized 是可重入的.可是不推薦,若是加上,會產生性能損耗.

關於這個方法的實現原理咱們仍是舉個例子比較好.假設咱們的數組長度爲3個,此時已經放滿數組了,以下:

[0,1,2]

此時tail = 2, 而後咱們繼續放入3,則數組以下:

[3,1,2],此時tail = 0. 而後咱們調用find.在該方法中會調用resolveTailEvent.

第1次傳入的是0,則index = (0+3-0)%3 = 0,得到的正是3.
第2次傳入的是1,則index = (0+3-1)%3 = 2,得到的正是2.
第3次傳入的是2,則index = (0+3-2)%3 = 1,得到的正是1.
所以說find(String, Date, String)得到的結果時按照添加的順序倒序返回的.

自動裝配:

聲明在AuditAutoConfiguration類內的static AuditEventRepositoryConfiguration配置類中,代碼以下:

@ConditionalOnMissingBean(AuditEventRepository.class)
protected static class AuditEventRepositoryConfiguration {

    @Bean
    public InMemoryAuditEventRepository auditEventRepository() throws Exception {
        return new InMemoryAuditEventRepository();
    }

}

 

當beanFactory中不存在 AuditEventRepository類型的bean時生效.註冊1個id爲auditEventRepository,類型爲InMemoryAuditEventRepository的bean.

2.六、AuditListener

AuditListener–> AbstractAuditListener的默認實現.監聽AuditApplicationEvent事件而後存儲到AuditEventRepository中. 代碼以下:

public class AuditListener extends AbstractAuditListener {

    private static final Log logger = LogFactory.getLog(AuditListener.class);

    private final AuditEventRepository auditEventRepository;

    public AuditListener(AuditEventRepository auditEventRepository) {
        this.auditEventRepository = auditEventRepository;
    }

    @Override
    protected void onAuditEvent(AuditEvent event) {
        if (logger.isDebugEnabled()) {
            logger.debug(event);
        }
        this.auditEventRepository.add(event);
    }

}

 

監聽到AuditApplicationEvent時,直接將其封裝的AuditEvent加入到AuditEventRepository中.仍是比較簡單的.

自動裝配以下:

在AuditAutoConfiguration中進行了聲明,代碼以下:

    @Bean
    @ConditionalOnMissingBean(AbstractAuditListener.class)
    public AuditListener auditListener() throws Exception {
        return new AuditListener(this.auditEventRepository);
    }

@Bean–> 註冊1個id爲auditListener,類型爲AuditListener的bean
@ConditionalOnMissingBean(AbstractAuditListener.class) –> 當beanFactory中不存在類型爲AbstractAuditListener的bean時生效。

注意,在AuditListener中注入的是InMemoryAuditEventRepository

2.7AbstractAuthenticationAuditListener

AbstractAuthenticationAuditListener–> 暴露 Spring Security AbstractAuthenticationEvent(認證事件) 將其轉換爲AuditEvent 的抽象ApplicationListener基類.

代碼以下:

public abstract class AbstractAuthenticationAuditListener implements
    ApplicationListener<AbstractAuthenticationEvent>, ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    protected ApplicationEventPublisher getPublisher() {
        return this.publisher;
    }

    protected void publish(AuditEvent event) {
        if (getPublisher() != null) {
            getPublisher().publishEvent(new AuditApplicationEvent(event));
        }
    }
}

 

2.八、AuthenticationAuditListener

AuthenticationAuditListener的默認實現
字段以下:

// 當發生AuthenticationSuccessEvent事件時添加到AuditEvent中的type
public static final String AUTHENTICATION_SUCCESS = "AUTHENTICATION_SUCCESS";

// 當發生AbstractAuthenticationFailureEvent事件時添加到AuditEvent中的type
public static final String AUTHENTICATION_FAILURE = "AUTHENTICATION_FAILURE";

// 當發生AuthenticationSwitchUserEvent事件時添加到AuditEvent中的type
public static final String AUTHENTICATION_SWITCH = "AUTHENTICATION_SWITCH";

private static final String WEB_LISTENER_CHECK_CLASS = "org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent";

private WebAuditListener webListener = maybeCreateWebListener();

// 只要加入spring-boot-starter-security的依賴,就會在當前類路徑下存在org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent
// 所以會返回WebAuditListener
private static WebAuditListener maybeCreateWebListener() {
if (ClassUtils.isPresent(WEB_LISTENER_CHECK_CLASS, null)) {
return new WebAuditListener();
}
return null;
}

 

onApplicationEvent 方法實現以下:

public void onApplicationEvent(AbstractAuthenticationEvent event) {
    // 1. 若是驗證失敗,
    if (event instanceof AbstractAuthenticationFailureEvent) {
        onAuthenticationFailureEvent((AbstractAuthenticationFailureEvent) event);
    }
    // 2.若是webListener不等於null.而且該事件爲AuthenticationSwitchUserEvent
    else if (this.webListener != null && this.webListener.accepts(event)) {
        this.webListener.process(this, event);
    }
    // 3. 若是是AuthenticationSuccessEvent
    else if (event instanceof AuthenticationSuccessEvent) {
        onAuthenticationSuccessEvent((AuthenticationSuccessEvent) event);
    }
}

  一、若是驗證失敗(AbstractAuthenticationFailureEvent),則發送AuditEvent事件,其type爲AUTHENTICATION_FAILURE.代碼以下:

private void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("type", event.getException().getClass().getName());
    data.put("message", event.getException().getMessage());
    if (event.getAuthentication().getDetails() != null) {
        data.put("details", event.getAuthentication().getDetails());
    }
    publish(new AuditEvent(event.getAuthentication().getName(),
            AUTHENTICATION_FAILURE, data));
}

  二、若是webListener不等於null.而且該事件爲AuthenticationSwitchUserEvent,則發送AuditEvent事件,其type爲AUTHENTICATION_SWITCH.代碼以下:

public void process(AuthenticationAuditListener listener,
        AbstractAuthenticationEvent input) {
    if (listener != null) {
        AuthenticationSwitchUserEvent event = (AuthenticationSwitchUserEvent) input;
        Map<String, Object> data = new HashMap<String, Object>();
        if (event.getAuthentication().getDetails() != null) {
            data.put("details", event.getAuthentication().getDetails());
        }
        data.put("target", event.getTargetUser().getUsername());
        listener.publish(new AuditEvent(event.getAuthentication().getName(),
                AUTHENTICATION_SWITCH, data));
    }

}

  三、若是是AuthenticationSuccessEvent,則發送AuditEvent事件,其type爲AUTHENTICATION_SUCCESS.代碼以下:

private void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
    Map<String, Object> data = new HashMap<String, Object>();
    if (event.getAuthentication().getDetails() != null) {
        data.put("details", event.getAuthentication().getDetails());
    }
    publish(new AuditEvent(event.getAuthentication().getName(),
            AUTHENTICATION_SUCCESS, data));
}

 

自動裝配:

在AuditAutoConfiguration中進行了聲明,代碼以下:

@Bean
@ConditionalOnClass(name = "org.springframework.security.authentication.event.AbstractAuthenticationEvent")
@ConditionalOnMissingBean(AbstractAuthenticationAuditListener.class)
public AuthenticationAuditListener authenticationAuditListener() throws Exception {
    return new AuthenticationAuditListener();
}

  一、@Bean –> 註冊1個id爲authenticationAuditListener, AuthenticationAuditListener的bean

  二、@ConditionalOnClass(name = 「org.springframework.security.authentication.event.AbstractAuthenticationEvent」)–> 當在當前類路徑下存在org.springframework.security.authentication.event.AbstractAuthenticationEvent時生效
  三、@ConditionalOnMissingBean(AbstractAuthenticationAuditListener.class)–>beanFactory中不存在AbstractAuthenticationAuditListener類型的bean時生效.

2.九、AbstractAuthorizationAuditListener

AbstractAuthorizationAuditListener –>1個暴露AbstractAuthorizationEvent(受權事件)做爲AuditEvent的抽象ApplicationListener基類.代碼以下:

public abstract class AbstractAuthorizationAuditListener implements
        ApplicationListener<AbstractAuthorizationEvent>, ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    protected ApplicationEventPublisher getPublisher() {
        return this.publisher;
    }

    protected void publish(AuditEvent event) {
        if (getPublisher() != null) {
            getPublisher().publishEvent(new AuditApplicationEvent(event));
        }
    }

}

 

2.十、AuthorizationAuditListener

AuthorizationAuditListener–> AbstractAuthorizationAuditListener的默認實現
字段以下:

// 發生AuthorizationFailureEvent事件時對應的AuditEvent的類型
public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";

onApplicationEvent代碼以下:

public void onApplicationEvent(AbstractAuthorizationEvent event) {
    // 1. 若是是AuthenticationCredentialsNotFoundEvent事件,則發送AuditEvent事件,type爲AUTHENTICATION_FAILURE
    if (event instanceof AuthenticationCredentialsNotFoundEvent) {
        onAuthenticationCredentialsNotFoundEvent(
                (AuthenticationCredentialsNotFoundEvent) event);
    }
    // 2. 若是是AuthorizationFailureEvent事件,則發送AuditEvent事件,type爲AUTHORIZATION_FAILURE
    else if (event instanceof AuthorizationFailureEvent) {
        onAuthorizationFailureEvent((AuthorizationFailureEvent) event);
    }
}

 

  一、若是是AuthenticationCredentialsNotFoundEvent事件,則發送AuditEvent事件,type爲AUTHENTICATION_FAILURE.代碼以下:

private void onAuthenticationCredentialsNotFoundEvent(
        AuthenticationCredentialsNotFoundEvent event) {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("type", event.getCredentialsNotFoundException().getClass().getName());
    data.put("message", event.getCredentialsNotFoundException().getMessage());
    publish(new AuditEvent("<unknown>",
            AuthenticationAuditListener.AUTHENTICATION_FAILURE, data));
}

 

  二、若是是AuthorizationFailureEvent事件,則發送AuditEvent事件,type爲AUTHORIZATION_FAILURE.代碼以下:

private void onAuthorizationFailureEvent(AuthorizationFailureEvent event) {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("type", event.getAccessDeniedException().getClass().getName());
    data.put("message", event.getAccessDeniedException().getMessage());
    if (event.getAuthentication().getDetails() != null) {
        data.put("details", event.getAuthentication().getDetails());
    }
    publish(new AuditEvent(event.getAuthentication().getName(), AUTHORIZATION_FAILURE,
            data));
}

 

自動裝配:

在AuditAutoConfiguration中進行了裝配,代碼以下:

@Bean
@ConditionalOnClass(name = "org.springframework.security.access.event.AbstractAuthorizationEvent")
@ConditionalOnMissingBean(AbstractAuthorizationAuditListener.class)
public AuthorizationAuditListener authorizationAuditListener() throws Exception {
    return new AuthorizationAuditListener();
}
  1. @Bean –> 註冊1個id爲authorizationAuditListener,類型爲AuthorizationAuditListener的bean
  2. @ConditionalOnClass(name = 「org.springframework.security.access.event.AbstractAuthorizationEvent」)–> 在當前類路徑下存在org.springframework.security.access.event.AbstractAuthorizationEvent時生效
  3. @ConditionalOnMissingBean(AbstractAuthorizationAuditListener.class)–> beanFactory中不存在AbstractAuthorizationAuditListener類型的bean時生效.

3、流程分析

準備工做
若是想讓 spring boot 應用激活AuditEvent的事件的處理,須要加入spring-boot-starter-security依賴,代碼以下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

光加入依賴還不夠,咱們須要加入security的配置,否則AuthorizationAuditListener,AuthenticationAuditListener 監聽什麼事件呢? 所以,咱們加入以下代碼:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/error-log").hasAuthority("ROLE_TEST").antMatchers("/", "/home")
                .permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and()
                .logout().logoutUrl("/logout").permitAll().and().authorizeRequests();

    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
    }
}

 

在configureGlobal中,咱們在內存中生成了1個用戶:用戶名爲user,密碼爲password,角色爲USER.
在configure中咱們配置了以下內容:

  1. 訪問/error-log須要擁有ROLE_TEST的權限
  2. 訪問/,/home不須要進行驗證
  3. 登陸頁面爲/login,不須要進行驗證
  4. 登出頁面爲/logout
  5. 其餘連接都須要進行驗證

聲明1個UserController,代碼以下:

@Controller
public class UserController {

    @RequestMapping("/")
    public String index() {
        return "index";
    }

    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login() {
        return "login";
    }

    @RequestMapping("/error-test")
    public String error() {
        return "1";
    }   
} 

 

在src/main/resources/templates目錄下建立以下幾個頁面:

hello.html,代碼以下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="註銷"/>
</form>
</body>
</html>

 

index.html,代碼以下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security入門</title>
</head>
<body>
<h1>歡迎使用Spring Security!</h1>
<p>點擊 <a th:href="@{/hello}">這裏</a> 打個招呼吧</p>
</body>
</html>

 

login.html,代碼以下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            用戶名或密碼錯
        </div>
        <div th:if="${param.logout}">
            您已註銷成功
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> 用戶名 : <input type="text" name="username"/> </label></div>
            <div><label> 密  碼 : <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="登陸"/></div>
        </form>
    </body>
</html>

 

測試

啓動應用後咱們訪問以下連接: http://127.0.0.1:8080/,返回的是以下頁面:

 

點擊index.html 中的超連接後,因爲須要進行驗證,返回到login頁面,如圖:

 

此時咱們輸入錯誤的用戶名,密碼,返回的頁面以下:

 

此時咱們輸入user,password 後,返回的頁面以下:

 

點擊註銷後,頁面以下:

 

此時咱們訪問 http://127.0.0.1:8080/error-test,因爲沒有登陸,仍是調回到登陸頁面.

訪問 http://127.0.0.1:8080/auditevents,返回的結果以下:

{
events: [
    {
    timestamp: "2018-01-23T03:52:13+0000",
    principal: "anonymousUser",
    type: "AUTHORIZATION_FAILURE",
    data: {
    details: {
    remoteAddress: "127.0.0.1",
    sessionId: null
    },
    type: "org.springframework.security.access.AccessDeniedException",
    message: "Access is denied"
    }
},
    {
    timestamp: "2018-01-23T03:54:21+0000",
    principal: "aaa",
    type: "AUTHENTICATION_FAILURE",
    data: {
    details: {
    remoteAddress: "127.0.0.1",
    sessionId: "DFDB023AEEF41BBD8079EC32402CBFD8"
    },
    type: "org.springframework.security.authentication.BadCredentialsException",
    message: "Bad credentials"
    }
    },
    {
    timestamp: "2018-01-23T03:55:50+0000",
    principal: "user",
    type: "AUTHENTICATION_SUCCESS",
    data: {
    details: {
    remoteAddress: "127.0.0.1",
    sessionId: "DFDB023AEEF41BBD8079EC32402CBFD8"
    }
    }
    },
    {
    timestamp: "2018-01-23T03:58:38+0000",
    principal: "anonymousUser",
    type: "AUTHORIZATION_FAILURE",
    data: {
    details: {
    remoteAddress: "127.0.0.1",
    sessionId: "6E6E614D638B6F5EE5B7E8CF516E2534"
    },
    type: "org.springframework.security.access.AccessDeniedException",
    message: "Access is denied"
    }
    },
    {
    timestamp: "2018-01-23T04:00:01+0000",
    principal: "anonymousUser",
    type: "AUTHORIZATION_FAILURE",
    data: {
    details: {
    remoteAddress: "127.0.0.1",
    sessionId: "6E6E614D638B6F5EE5B7E8CF516E2534"
    },
    type: "org.springframework.security.access.AccessDeniedException",
    message: "Access is denied"
    }
    },
    {
    timestamp: "2018-01-23T04:00:12+0000",
    principal: "user",
    type: "AUTHENTICATION_SUCCESS",
    data: {
    details: {
    remoteAddress: "127.0.0.1",
    sessionId: "6E6E614D638B6F5EE5B7E8CF516E2534"
    }
    }
    }
]
}

 

解析

  1. 當咱們訪問 http://127.0.0.1:8080/hello.html 時,因爲須要驗證,所以會發送AuthorizationFailureEvent事件,此時會交由AuthorizationAuditListener處理,調用onAuthorizationFailureEvent方法.發送AuditEvent事件
  2. AuditListener 監聽該事件,最終執行onAuditEvent方法,將AuditEvent存入到InMemoryAuditEventRepository中.
  3. 此時在login.html中,咱們輸入錯誤的用戶名,密碼,此時會發送AuthenticationFailureBadCredentialsEvent事件,交由AuthenticationAuditListener處理,最終執行onAuthenticationFailureEvent方法,發送AuditEvent事件, 最終仍是在AuditListener中將事件存到 InMemoryAuditEventRepository中
  4. 此時咱們輸入正確的用戶名密碼後,會發送AuthenticationSuccessEvent事件,交由AuthenticationAuditListener事件處理,最終執行onAuthenticationSuccessEvent方法,發送AuditEvent事件,最終仍是在AuditListener中將事件存到InMemoryAuditEventRepository中
  5. 以後產生了SessionFixationProtectionEvent事件,AuthenticationAuditListener不進行處理
  6. 產生InteractiveAuthenticationSuccessEvent,AuthenticationAuditListener不進行處理.
  7. 此時跳轉到hello.html ,咱們點擊註銷後,沒有產生任何的事件.
  8. 登陸成功後,咱們訪問/error-log,因爲須要ROLE_TEST權限,而user 只有ROLE_USER 權限,所以會產生AuthorizationFailureEvent事件,所以會交由AuthorizationAuditListener處理,發送AuditEvent事件,最終仍是在AuditListener中將事件存到InMemoryAuditEventRepository中

zhuan:https://blog.csdn.net/qq_26000415/article/details/79138270

相關文章
相關標籤/搜索