电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

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

Spring Security 自動踢掉前一個登錄用戶的實現(xiàn)代碼

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

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

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

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

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

1.需求分析

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

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

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

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

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

2.具體實現(xiàn)

2.1 踢掉已經(jīng)登錄用戶

想要用新的登錄踢掉舊的登錄,我們只需要將最大會話數(shù)設置為 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 表示配置最大會話數(shù)為 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 已經(jīng)過期,原因則是由于使用同一個用戶進行并發(fā)登錄。

2.2 禁止新的登錄

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

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

為了解決這一問題,我們提供一個 HttpSessionEventPublisher ,這個類實現(xiàn)了 HttpSessionListener 接口,在該 Bean 中,可以將 session 創(chuàng)建以及銷毀的事件及時感知到,并且調(diào)用 Spring 中的事件機制將相關的創(chuàng)建和銷毀事件發(fā)布出去,進而被 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.實現(xiàn)原理

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

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

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);

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

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();}}}

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

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

4.小結

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

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

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

標簽: Spring
相關文章:
主站蜘蛛池模板: RS系列电阻器,RK_RJ启动调整电阻器,RQ_RZ电阻器-上海永上电器有限公司 | led全彩屏-室内|学校|展厅|p3|户外|会议室|圆柱|p2.5LED显示屏-LED显示屏价格-LED互动地砖屏_蕙宇屏科技 | 减速机电机一体机_带电机减速器一套_德国BOSERL电动机与减速箱生产厂家 | 精密线材测试仪-电线电缆检测仪-苏州欣硕电子科技有限公司 | 通风气楼_通风天窗_屋顶风机-山东美创通风设备有限公司 | 四川实木门_成都实木门 - 蓬溪聚成门业有限公司 | led全彩屏-室内|学校|展厅|p3|户外|会议室|圆柱|p2.5LED显示屏-LED显示屏价格-LED互动地砖屏_蕙宇屏科技 | 合肥活动房_安徽活动板房_集成打包箱房厂家-安徽玉强钢结构集成房屋有限公司 | 加气混凝土砌块设备,轻质砖设备,蒸养砖设备,新型墙体设备-河南省杜甫机械制造有限公司 | 电子海图系统-电梯检验系统-智慧供热系统开发-商品房预售资金监管系统 | 中视电广_短视频拍摄_短视频推广_短视频代运营_宣传片拍摄_影视广告制作_中视电广 | 模具ERP_模具管理系统_模具mes_模具进度管理_东莞市精纬软件有限公司 | 成都珞石机械 - 模温机、油温机、油加热器生产厂家 | 京马网,京马建站,网站定制,营销型网站建设,东莞建站,东莞网站建设-首页-京马网 | 合肥白癜风医院_合肥治疗白癜风医院_合肥看白癜风医院哪家好_合肥华研白癜风医院 | pbt头梳丝_牙刷丝_尼龙毛刷丝_PP塑料纤维合成毛丝定制厂_广州明旺 | 高光谱相机-近红外高光谱相机厂家-高光谱成像仪-SINESPEC 赛斯拜克 | 伶俐嫂培训学校_月嫂培训班在哪里报名学费是多少_月嫂免费政府培训中心推荐 | 物流之家新闻网-最新物流新闻|物流资讯|物流政策|物流网-匡匡奈斯物流科技 | 实验室隔膜泵-无油防腐蚀隔膜泵-耐腐蚀隔膜真空泵-杭州景程仪器 电杆荷载挠度测试仪-电杆荷载位移-管桩测试仪-北京绿野创能机电设备有限公司 | 美国查特CHART MVE液氮罐_查特杜瓦瓶_制造全球品质液氮罐 | 健身器材-健身器材厂家专卖-上海七诚健身器材有限公司 | 电主轴-高速精密电主轴-高速电机厂家-瑞德沃斯品牌有限公司 | 内窥镜-工业内窥镜厂家【上海修远仪器仪表有限公司】 | 品牌策划-品牌设计-济南之式传媒广告有限公司官网-提供品牌整合丨影视创意丨公关活动丨数字营销丨自媒体运营丨数字营销 | 艺术涂料_进口艺术涂料_艺术涂料加盟_艺术涂料十大品牌 -英国蒙太奇艺术涂料 | 今日热点_实时热点_奇闻异事_趣闻趣事_灵异事件 - 奇闻事件 | 桑茶-七彩贝壳桑叶茶 长寿茶 | 伊卡洛斯软装首页-电动窗帘,别墅窗帘,定制窗帘,江浙沪1000+别墅窗帘案例 | 游动电流仪-流通式浊度分析仪-杰普仪器(上海)有限公司 | 高精度-恒温冷水机-螺杆式冰水机-蒸发冷冷水机-北京蓝海神骏科技有限公司 | 诸城网站建设-网络推广-网站优化-阿里巴巴托管-诸城恒泰互联 | QQ房产导航-免费收录优秀房地产网站_房地产信息网 | 交联度测试仪-湿漏电流测试仪-双85恒温恒湿试验箱-常州市科迈实验仪器有限公司 | 天津市能谱科技有限公司-专业的红外光谱仪_红外测油仪_紫外测油仪_红外制样附件_傅里叶红外光谱技术生产服务厂商 | 植筋胶-粘钢胶-碳纤维布-碳纤维板-环氧砂浆-加固材料生产厂家-上海巧力建筑科技有限公司 | 华溶溶出仪-Memmert稳定箱-上海协烁仪器科技有限公司 | 食品机械专用传感器-落料放大器-低价接近开关-菲德自控技术(天津)有限公司 | 智能案卷柜_卷宗柜_钥匙柜_文件流转柜_装备柜_浙江福源智能科技有限公司 | 热工多功能信号校验仪-热电阻热电偶校验仿真仪-金湖虹润仪表 | 厚壁钢管-厚壁无缝钢管-小口径厚壁钢管-大口径厚壁钢管 - 聊城宽达钢管有限公司 |