spring中"投機取巧"地限制 用戶同時登陸

公衆號原創文章 

開發背景:項目中採用spring session + spring security 方式做登陸註冊 ,現在要求後臺用戶只能同時一個人登陸

 

苦難的經歷:spring security 框架比較重,難以 快速深入理解, 另外 網上 spring security 如何限制 用戶同時登陸 的 文章 又是 一大堆,只想抱着試試看的態度快速成功,可 在項目中 實踐 配置 好多種情況 都絲毫不起作用 ,加斷點調試 源碼 ,發現 配置 過濾器(filter)生效了,但是 過濾器中的具體策略(strategy)卻根本 都沒有執行 ,一度陷入了迷茫 。

 

苦海脫生 : 下班了都,沒辦法 ,只能 利用晚上時間體系去學習spring security 了,但估計時間 短不了 ,怎麼辦 ?找經典 博客快速突擊,我一直認爲 公衆號的文章都是最新最優秀的。哈哈哈 ,還真讓我幸運得碰到了 ,不出幾篇文章 ,就找到了  關於 spring security 的經典文章 。https://toutiao.io/posts/idtbcp

 

看完框架解讀第一篇,核心 類第二篇 還沒看完 ,因爲項目代碼已經成竹在胸了,我就明白 我一下午 慌亂採坑所在了 。

 

原來,我們接入spring security的流程並不標準, 沒有用原生的方式UsernamePasswordAuthenticationFilter 實現 登錄驗證,而是 代碼中簡單比對後 直接 儲存session,所以網上的ConcurrentSessionControlAuthenticationStrategy策略配置根本無法生效 ,所以 一下午的研究白費了 。

 

 

海高憑魚躍 :

 

經過上面的一番苦戰 ,思路就已經有了 ,第一種方法 是 按照 標準 方法 重新 接入 spring security ,那網上的文章 配置肯定生效啊(費時)

 

第二種 方法就是 嘗試直接 操作 spring session ,跳過 spring security 。

關鍵 是我在 spring session的 公衆號 還真看到了根據用戶名查找用戶歸屬的SESSION 的段落(從零開始的Spring Session

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
@Autowired    FindByIndexNameSessionRepository<? extends ExpiringSession> sessionRepository;    @RequestMapping("/test/findByUsername")    @ResponseBody    public Map findByUsername (@RequestParam String username)    {        Map<String, ? extends ExpiringSession> usersSessions = sessionRepository                .findByIndexNameAndIndexValue(FindByIndexNameSessionRepository                        .PRINCIPAL_NAME_INDEX_NAME, username);        return usersSessions;    }

那還說什麼 ,第二天去實踐唄

 

真槍實戰 :

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
/**     * 保存user到session中     * @param user     * @return     */    public static String savePrincipal(Auditor user) {        AuthUserDetails authUserDetails = new AuthUserDetails(user);        return savePrincipal(authUserDetails);    }
    /**     * 保存principal     * @param principal     * @return     */    public static String savePrincipal(AuthUserDetails principal) {        String SESSION_USER_KEY = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;        SecurityContextImpl context = new SecurityContextImpl();        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, "dummy credentials", Collections.emptyList());        authentication.setDetails(new WebAuthenticationDetails(getRequest()));        context.setAuthentication(authentication);        getSession().setAttribute(SESSION_USER_KEY, context);        return getSession().getId();    }

 

認真 攻讀 了保存session的代碼 ,找到了最終往redis保存的源代碼 RedisOperationsSessionRepository,還真有 上面所說的

FindByIndexNameSessionRepository 和 期待的delete 方法 ,那隻要研究一下 非標準接入 情況下FindByIndexNameSessionRepository 第二個參數 username該傳 什麼值就可以了

通過閱讀save 方法 和 FindByIndexNameSessionRepository 對比,

  •  
  •  
  •  
String getPrincipalKey(String principalName) {        return this.keyPrefix + "index:" + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME + ":" + principalName;    }

 

找到了第二個參數 生成方法,打斷點知道了FindByIndexNameSessionRepository 應該傳什麼值,就是生成 UsernamePasswordAuthenticationToken的AuthUserDetails,基本大功告成 。

 

在登錄時 我們只需要把 上一個用戶踢掉就可以了,就可以實現功能了

 

最終登錄時的部分代碼 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
@Override    public void  forcedOffLine(Long auditorId){        final Auditor auditor = this.findById(auditorId);        AuthUserDetails authUserDetails = new AuthUserDetails(auditor);        final Map<String, ? extends ExpiringSession> sessions = redisOperationsSessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, authUserDetails.toString());        log.info("刪掉{}用戶session爲{}",auditorId,sessions);        if(!org.springframework.util.CollectionUtils.isEmpty(sessions)){            for ( String key : sessions.keySet()){                redisOperationsSessionRepository.delete(key);            }        }    }

 

總結:

本文簡述瞭如何實現登錄功能的思想歷程,懂得了還是應該先整體瞭解後再深入實踐的道理

 


後記:關於 redis 總結的第三篇文章沒發出來,是因爲 瞭解redis單線程模型 去 學習了網絡編程,後續兩篇文章同時發出

 

如果喜歡,歡迎關注我的公衆號:喬志勇筆記

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章