Spring Security之屢次登陸失敗後帳戶鎖定功能的實現

file

在上一次寫的文章中,爲你們說到了如何動態的從數據庫加載用戶、角色、權限信息,從而實現登陸驗證及受權。在實際的開發過程當中,咱們一般會有這樣的一個需求:當用戶屢次登陸失敗的時候,咱們應該將帳戶鎖定,等待必定的時間以後才能再次進行登陸操做。html

1、基礎知識回顧

要實現屢次登陸失敗帳戶鎖定的功能,咱們須要先回顧一下基礎知識:前端

  • Spring Security 不須要咱們本身實現登陸驗證邏輯,而是將用戶、角色、權限信息以實現UserDetails和UserDetailsService接口的方式告知Spring Security。具體的登陸驗證邏輯Spring Security 會幫助咱們實現。
  • UserDetails接口中有一個方法叫作isAccountNonLocked()用於判斷帳號是否被鎖定,也就是說咱們應該經過該方法對應的set方法setAccountNonLocked(false)告知Spring Security該登陸帳戶被鎖定。
  • 那麼應該在哪裏判斷帳號登陸失敗的次數並執行鎖定機制呢?固然是咱們以前文章給你們介紹的《自定義登陸成功及失敗結果處理》的AuthenticationFailureHandler。

建議您先閱讀本文,若是您對本文的實現過程感到迷惑,建議您再翻看本號以前的相關內容。mysql

2、實現屢次登陸失敗鎖定的原理

通常來講實現這個需求,咱們須要針對每個用戶記錄登陸失敗的次數nLock和鎖定帳戶的到期時間releaseTime。具體你是把這2個信息存儲在mysql、仍是文件中、仍是redis中等等,徹底取決於你對你所處的應用架構適用性的判斷。具體的實現邏輯無非就是:redis

  • 登錄失敗以後,從存儲中將nLock取出來加1。
  • 若是nLock大於登錄失敗閾值(好比3次),則將nLock=0,而後設置releaseTime爲當前時間加上鎖定週期。經過setAccountNonLocked(false)告知Spring Security該登陸帳戶被鎖定。
  • 若是nLock小於等於1,則將nLock再次存起來。
  • 在一個合適的時機,將鎖定狀態重置爲setAccountNonLocked(true)。

這是一種很是典型的實現方式,筆者向你們介紹一款很是有用的開源軟件叫作:ratelimitj。這個軟件的功能主要是爲API訪問進行限流,也就是說能夠經過制定規則限制API接口的訪問頻率。那剛好登陸驗證接口也是API的一種啊,咱們正好也須要限制它在必定的時間內的訪問次數。spring

3、具體實現

首先須要將ratelimitj經過maven座標引入到咱們的應用裏面來。咱們使用的是內存存儲的版本,還有redis存儲的版本,你們能夠根據本身的應用狀況選用。sql

<dependency>
            <groupid>es.moki.ratelimitj</groupid>
            <artifactid>ratelimitj-inmemory</artifactid>
            <version>0.4.1</version>
        </dependency>

以後經過繼承SimpleUrlAuthenticationFailureHandler ,實現onAuthenticationFailure方法。該實現是針對登陸失敗的結果的處理,在咱們以前的文章中已經講過。數據庫

@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    UserDetailsManager userDetailsManager;

    //規則定義:1小時以內5次機會,就觸發限流行爲
    Set<requestlimitrule> rules = 
            Collections.singleton(RequestLimitRule.of(1 * 60, TimeUnit.MINUTES,5)); 
    RequestRateLimiter limiter = new InMemorySlidingWindowRequestRateLimiter(rules);


    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response, 
                                        AuthenticationException exception) 
                                        throws IOException, ServletException {

         String userId = //從request或request.getSession中獲取登陸用戶名
         //計數器加1,並判斷該用戶是否已經到了觸發了鎖定規則
         boolean reachLimit = limiter.overLimitWhenIncremented(userId);

        if(reachLimit){ //若是觸發了鎖定規則,經過UserDetails告知Spring Security鎖定帳戶
               user.setAccountNonLocked(false);
               userDetailsManager.updateUser(user);
               SysUser user = (SysUser) userDetailsManager.loadUserByUsername(userId);
        }
        

        
        //此處省略經過response作json或html響應
    }
}
  • 核心實現注意看代碼中的註釋
  • 代碼中的SysUser爲UserDetails的實現類,若是不知道如何實現請參考本號以前的文章
  • userDetailsManager被用於管理UserDetails信息,經過改變UserDetails改變Spring Security驗證行爲。

4、重置鎖定狀態的時機

user.setAccountNonLocked(true);

重置鎖定狀態很簡單,就是上面的代碼。可是更重要的是如何選擇重置鎖定狀態的時機。筆者能想到幾種方案以下json

  • 下一次登錄的時候,自定義過濾器,加在Spring Boot過濾器鏈最前端作鎖定狀態重置的判斷。
  • 當登陸帳戶被鎖定以後,以後用戶的每一次登陸都會拋出LockedException。咱們徹底能夠經過Spring Boot的全局異常捕獲機制,在其中捕獲LockedException,並作鎖定狀態的判斷及重置行爲。
  • 寫一個Spring 的定時器輪詢,固然這是最差的方案。

期待您的關注

相關文章
相關標籤/搜索