日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

Spring Security 自動踢掉前一個登錄用戶的實現代碼

瀏覽:166日期:2023-09-06 17:19:04

登錄成功后,自動踢掉前一個登錄用戶,松哥第一次見到這個功能,就是在扣扣里邊見到的,當時覺得挺好玩的。

自己做開發后,也遇到過一模一樣的需求,正好最近的 Spring Security 系列正在連載,就結合 Spring Security 來和大家聊一聊這個功能如何實現。

本文是本系列的第十三篇,閱讀前面文章有助于更好的理解本文:

挖一個大坑,Spring Security 開搞! 松哥手把手帶你入門 Spring Security,別再問密碼怎么解密了 手把手教你定制 Spring Security 中的表單登錄 Spring Security 做前后端分離,咱就別做頁面跳轉了!統統 JSON 交互 Spring Security 中的授權操作原來這么簡單 Spring Security 如何將用戶數據存入數據庫? Spring Security+Spring Data Jpa 強強聯手,安全管理只有更簡單! Spring Boot + Spring Security 實現自動登錄功能 Spring Boot 自動登錄,安全風險要怎么控制? 在微服務項目中,Spring Security 比 Shiro 強在哪? SpringSecurity 自定義認證邏輯的兩種方式(高級玩法) Spring Security 中如何快速查看登錄用戶 IP 地址等信息?

1.需求分析

在同一個系統中,我們可能只允許一個用戶在一個終端上登錄,一般來說這可能是出于安全方面的考慮,但是也有一些情況是出于業務上的考慮,松哥之前遇到的需求就是業務原因要求一個用戶只能在一個設備上登錄。

要實現一個用戶不可以同時在兩臺設備上登錄,我們有兩種思路:

后來的登錄自動踢掉前面的登錄,就像大家在扣扣中看到的效果。 如果用戶已經登錄,則不允許后來者登錄。

這種思路都能實現這個功能,具體使用哪一個,還要看我們具體的需求。

在 Spring Security 中,這兩種都很好實現,一個配置就可以搞定。

2.具體實現

2.1 踢掉已經登錄用戶

想要用新的登錄踢掉舊的登錄,我們只需要將最大會話數設置為 1 即可,配置如下:

@Overrideprotected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage('/login.html') .permitAll() .and() .csrf().disable() .sessionManagement() .maximumSessions(1);}

maximumSessions 表示配置最大會話數為 1,這樣后面的登錄就會自動踢掉前面的登錄。這里其他的配置都是我們前面文章講過的,我就不再重復介紹,文末可以下載案例完整代碼。

配置完成后,分別用 Chrome 和 Firefox 兩個瀏覽器進行測試(或者使用 Chrome 中的多用戶功能)。

Chrome 上登錄成功后,訪問 /hello 接口。 Firefox 上登錄成功后,訪問 /hello 接口。 在 Chrome 上再次訪問 /hello 接口,此時會看到如下提示:

This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).

可以看到,這里說這個 session 已經過期,原因則是由于使用同一個用戶進行并發登錄。

2.2 禁止新的登錄

如果相同的用戶已經登錄了,你不想踢掉他,而是想禁止新的登錄操作,那也好辦,配置方式如下:

@Overrideprotected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage('/login.html') .permitAll() .and() .csrf().disable() .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(true);}

添加 maxSessionsPreventsLogin 配置即可。此時一個瀏覽器登錄成功后,另外一個瀏覽器就登錄不了了。

是不是很簡單?

不過還沒完,我們還需要再提供一個 Bean:

@BeanHttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher();}

為什么要加這個 Bean 呢?因為在 Spring Security 中,它是通過監聽 session 的銷毀事件,來及時的清理 session 的記錄。用戶從不同的瀏覽器登錄后,都會有對應的 session,當用戶注銷登錄之后,session 就會失效,但是默認的失效是通過調用 StandardSession#invalidate 方法來實現的,這一個失效事件無法被 Spring 容器感知到,進而導致當用戶注銷登錄之后,Spring Security 沒有及時清理會話信息表,以為用戶還在線,進而導致用戶無法重新登錄進來(小伙伴們可以自行嘗試不添加上面的 Bean,然后讓用戶注銷登錄之后再重新登錄)。

為了解決這一問題,我們提供一個 HttpSessionEventPublisher ,這個類實現了 HttpSessionListener 接口,在該 Bean 中,可以將 session 創建以及銷毀的事件及時感知到,并且調用 Spring 中的事件機制將相關的創建和銷毀事件發布出去,進而被 Spring Security 感知到,該類部分源碼如下:

public void sessionCreated(HttpSessionEvent event) {HttpSessionCreatedEvent e = new HttpSessionCreatedEvent(event.getSession());getContext(event.getSession().getServletContext()).publishEvent(e);}public void sessionDestroyed(HttpSessionEvent event) {HttpSessionDestroyedEvent e = new HttpSessionDestroyedEvent(event.getSession());getContext(event.getSession().getServletContext()).publishEvent(e);}

OK,雖然多了一個配置,但是依然很簡單!

3.實現原理

上面這個功能,在 Spring Security 中是怎么實現的呢?我們來稍微分析一下源碼。

首先我們知道,在用戶登錄的過程中,會經過 UsernamePasswordAuthenticationFilter(參考: Spring Security 登錄流程),而 UsernamePasswordAuthenticationFilter 中過濾方法的調用是在 AbstractAuthenticationProcessingFilter 中觸發的,我們來看下 AbstractAuthenticationProcessingFilter#doFilter 方法的調用:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}Authentication authResult;try {authResult = attemptAuthentication(request, response);if (authResult == null) {return;}sessionStrategy.onAuthentication(authResult, request, response);}catch (InternalAuthenticationServiceException failed) {unsuccessfulAuthentication(request, response, failed);return;}catch (AuthenticationException failed) {unsuccessfulAuthentication(request, response, failed);return;}// Authentication successif (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}successfulAuthentication(request, response, chain, authResult);

在這段代碼中,我們可以看到,調用 attemptAuthentication 方法走完認證流程之后,回來之后,接下來就是調用 sessionStrategy.onAuthentication 方法,這個方法就是用來處理 session 的并發問題的。具體在:

public class ConcurrentSessionControlAuthenticationStrategy implementsMessageSourceAware, SessionAuthenticationStrategy {public void onAuthentication(Authentication authentication,HttpServletRequest request, HttpServletResponse response) {final List<SessionInformation> sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);int sessionCount = sessions.size();int allowedSessions = getMaximumSessionsForThisUser(authentication);if (sessionCount < allowedSessions) {// They haven’t got too many login sessions running at presentreturn;}if (allowedSessions == -1) {// We permit unlimited loginsreturn;}if (sessionCount == allowedSessions) {HttpSession session = request.getSession(false);if (session != null) {// Only permit it though if this request is associated with one of the// already registered sessionsfor (SessionInformation si : sessions) {if (si.getSessionId().equals(session.getId())) {return;}}}// If the session is null, a new one will be created by the parent class,// exceeding the allowed number}allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);}protected void allowableSessionsExceeded(List<SessionInformation> sessions,int allowableSessions, SessionRegistry registry)throws SessionAuthenticationException {if (exceptionIfMaximumExceeded || (sessions == null)) {throw new SessionAuthenticationException(messages.getMessage('ConcurrentSessionControlAuthenticationStrategy.exceededAllowed',new Object[] {allowableSessions},'Maximum sessions of {0} for this principal exceeded'));}// Determine least recently used sessions, and mark them for invalidationsessions.sort(Comparator.comparing(SessionInformation::getLastRequest));int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);for (SessionInformation session: sessionsToBeExpired) {session.expireNow();}}}

這段核心代碼我來給大家稍微解釋下:

首先調用 sessionRegistry.getAllSessions 方法獲取當前用戶的所有 session,該方法在調用時,傳遞兩個參數,一個是當前用戶的 authentication,另一個參數 false 表示不包含已經過期的 session(在用戶登錄成功后,會將用戶的 sessionid 存起來,其中 key 是用戶的主體(principal),value 則是該主題對應的 sessionid 組成的一個集合)。 接下來計算出當前用戶已經有幾個有效 session 了,同時獲取允許的 session 并發數。 如果當前 session 數(sessionCount)小于 session 并發數(allowedSessions),則不做任何處理;如果 allowedSessions 的值為 -1,表示對 session 數量不做任何限制。 如果當前 session 數(sessionCount)等于 session 并發數(allowedSessions),那就先看看當前 session 是否不為 null,并且已經存在于 sessions 中了,如果已經存在了,那都是自家人,不做任何處理;如果當前 session 為 null,那么意味著將有一個新的 session 被創建出來,屆時當前 session 數(sessionCount)就會超過 session 并發數(allowedSessions)。 如果前面的代碼中都沒能 return 掉,那么將進入策略判斷方法 allowableSessionsExceeded 中。 allowableSessionsExceeded 方法中,首先會有 exceptionIfMaximumExceeded 屬性,這就是我們在 SecurityConfig 中配置的 maxSessionsPreventsLogin 的值,默認為 false,如果為 true,就直接拋出異常,那么這次登錄就失敗了(對應 2.2 小節的效果),如果為 false,則對 sessions 按照請求時間進行排序,然后再使多余的 session 過期即可(對應 2.1 小節的效果)。

4.小結

如此,兩行簡單的配置就實現了 Spring Security 中 session 的并發管理。是不是很簡單?不過這里還有一個小小的坑,松哥將在下篇文章中繼續和大家分析。

本文案例大家可以從 GitHub 上下載:https://github.com/lenve/spring-security-samples

到此這篇關于Spring Security 自動踢掉前一個登錄用戶的實現代碼的文章就介紹到這了,更多相關Spring Security 踢掉登錄用戶內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Spring
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
国产情侣一区在线| 国产成人精品福利| 欧美aa在线观看| 精品亚洲二区| 欧美黑人做爰爽爽爽| 国产欧美日韩影院| 国产精品网在线观看| 国产精品最新自拍| 国产精品免费精品自在线观看| 国产欧美日韩影院| 久久永久免费| 日韩理论片av| 99精品电影| 国产偷自视频区视频一区二区| 亚洲欧美网站| 亚欧洲精品视频在线观看| 日韩一二三区在线观看| 亚洲精品欧美| 欧美亚洲三区| 丁香婷婷久久| 亚洲午夜91| 免费不卡在线视频| 日韩av一区二区在线影视| 国产亚洲一区二区三区不卡| 精品理论电影在线| 999久久久91| 亚洲精品护士| 精品视频一区二区三区在线观看| 樱桃视频成人在线观看| 久久一区二区三区喷水| 亚洲一二av| 美女视频网站久久| 日韩av首页| 久久国产精品99国产| 日本91福利区| 欧产日产国产精品视频| 久久av在线| 国产乱人伦丫前精品视频| 日韩av福利| 中文字幕av一区二区三区四区| 国产精品成人3p一区二区三区| 日本不卡免费高清视频在线| 模特精品在线| 你懂的国产精品永久在线| 神马日本精品| 日韩不卡手机在线v区| 9999国产精品| 日本大胆欧美人术艺术动态| 91p九色成人| 久久久久国产精品一区二区| 天堂久久av| 国产激情在线播放| 亚洲综合精品四区| 国产精品v一区二区三区| 国产99精品| 欧美久久亚洲| 99国产精品99久久久久久粉嫩| 久久伊人久久| 日韩午夜av在线| 免费精品一区| 影院欧美亚洲| 麻豆91在线播放| 99pao成人国产永久免费视频 | 日本不卡一区二区| 97精品国产福利一区二区三区| 免费日韩av片| 中文字幕一区久| 日本中文字幕不卡| 在线观看精品| 91精品美女| 欧美日韩国产探花| 精品一区二区三区视频在线播放| 激情婷婷久久| 麻豆传媒一区二区三区| 日韩在线a电影| 麻豆mv在线观看| 亚洲精品无吗| 日韩精品影视| 老色鬼精品视频在线观看播放| 久久国产99| 亚洲人成在线网站| 国产经典一区| 亚洲婷婷丁香| 不卡中文一二三区| 久久亚洲道色| 日韩av在线免费观看不卡| 国产婷婷精品| 欧美亚洲激情| av日韩中文| 国产精品99久久久久久董美香| 日韩专区在线视频| 日韩一区欧美| 久久字幕精品一区| 日韩高清二区| 蜜臀91精品一区二区三区| 久久久久久久久久久9不雅视频| 国产精品久久国产愉拍| 亚洲精品无播放器在线播放| 美女久久久久| 久久男女视频| 国产一二在线播放| 久久亚洲黄色| 美女视频一区在线观看| 日韩av黄色在线| 亚洲综合专区| 美女精品在线| 神马日本精品| 色爱综合av| 日韩欧美网址| 日韩国产综合| 日本欧美不卡| 成人羞羞在线观看网站| 婷婷综合六月| 日韩高清不卡一区二区| 亚洲一区二区三区四区电影| 日韩视频在线一区二区三区| 成人av动漫在线观看| 久久久国产精品一区二区中文| 国产中文在线播放| 成人美女视频| 超碰超碰人人人人精品| 国产伊人久久| 国产成人久久精品麻豆二区| 久久精品国产久精国产爱| 美女久久精品| 欧美国产小视频| 在线天堂中文资源最新版| 视频一区二区国产| 亚洲一区二区av| 日韩中文字幕一区二区三区| 亚洲人成网77777色在线播放| 亚洲三级精品| 美国三级日本三级久久99| 亚洲一区导航| 97久久超碰| 久久99视频| 久久精品高清| 久久精品在线| 久久午夜影视| 日韩精品乱码av一区二区| 久久国产精品美女| 成人精品国产亚洲| 91看片一区| 国产农村妇女精品一区二区| 一区二区三区国产在线| 国产精品网址| 国产超碰精品| 久久先锋影音| 国产欧美一区二区精品久久久 | 无码日韩精品一区二区免费| 五月激激激综合网色播| 国产极品久久久久久久久波多结野| 久久精品国产精品亚洲毛片| 色婷婷狠狠五月综合天色拍| 美女久久网站| 国产精品magnet| 婷婷激情久久| 日韩在线a电影| 国产激情综合| 在线日韩视频| 亚洲精一区二区三区| 国产精品v日韩精品v欧美精品网站| 裤袜国产欧美精品一区| 久久成人国产| 久久精品国产成人一区二区三区| 日韩av首页| 日韩精品高清不卡| 日韩免费av| 四虎成人精品一区二区免费网站| 麻豆免费精品视频| 亚洲不卡av不卡一区二区| 日韩一区精品视频| 精品三区视频| 亚洲一卡久久| 里番精品3d一二三区| 91九色精品| 国产视频一区二| 91精品亚洲| 日韩av成人高清| 久久精品影视| 日韩高清中文字幕一区| 热三久草你在线| 日韩国产精品久久久久久亚洲| 亚洲综合电影| 日韩欧美激情| 久久国产日本精品| 欧美久久久网站| 激情五月综合网| 国产精品午夜av| 99在线精品视频在线观看| 麻豆精品一区二区综合av| 久久www成人_看片免费不卡| 精品国产日韩欧美精品国产欧美日韩一区二区三区 | 日韩中文字幕无砖| 久久久久久夜| 日韩欧美中文字幕一区二区三区| 日韩精品不卡一区二区| 日韩二区在线观看| 欧美1区2区3区| 九九九精品视频|