SpringSecurity-11-只允許一個(gè)用戶登錄

SpringSecurity-11-只允許一個(gè)用戶登錄

本次給你介紹只允許用戶在一個(gè)地方登錄止潘,也就是說每個(gè)用戶只允許有一個(gè)Session。他有兩種場(chǎng)景

  • 如果同一個(gè)用戶在第二個(gè)地方登錄幌甘,則將第一個(gè)登錄下線
  • 如果同一個(gè)用戶在第二個(gè)地方登錄,則不允許二次的登錄

同一個(gè)用戶在第二個(gè)地方登錄,則將第一個(gè)登錄退出

具體步驟如下:

  1. 重構(gòu)com.security.learn.config.LearnSrpingSecurity的configure(HttpSecurity http)方法
?????@Autowired
????private?SessionInformationExpiredStrategy?sessionInformationExpiredStrategy;
????????????
????????????.and()
????????????????.sessionManagement()
????????????????.invalidSessionStrategy(invalidSessionStrategy)
????????????????.maximumSessions(1)////?每個(gè)用戶在系統(tǒng)中的最大session數(shù)
???????????????//?.maxSessionsPreventsLogin(true)
????????????????.expiredSessionStrategy(sessionInformationExpiredStrategy)//?當(dāng)用戶達(dá)到最大session數(shù)后吃谣,則調(diào)用此處的實(shí)現(xiàn)
  • 自定義SessionInformationExpiredStrategy實(shí)現(xiàn)類來定制策略
import?com.fasterxml.jackson.databind.ObjectMapper;
import?com.security.learn.handler.MyAuthenticationFailureHandler;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.security.authentication.AuthenticationServiceException;
import?org.springframework.security.core.AuthenticationException;
import?org.springframework.security.core.userdetails.UserDetails;
import?org.springframework.security.web.session.SessionInformationExpiredEvent;
import?org.springframework.security.web.session.SessionInformationExpiredStrategy;

import?javax.servlet.ServletException;
import?java.io.IOException;

public?class?MySessionInformationExpiredStrategy?implements?SessionInformationExpiredStrategy?{

????//Jackson?JSON數(shù)據(jù)處理類
????private??static?ObjectMapper?objectMapper?=?new?ObjectMapper();

????@Autowired
????MyAuthenticationFailureHandler?myAuthenticationFailureHandler;
????@Override
????public?void?onExpiredSessionDetected(SessionInformationExpiredEvent?event)?throws?IOException,?ServletException?{
????????//?1.?獲取用戶名
????????UserDetails?userDetails?=
????????????????(UserDetails)event.getSessionInformation().getPrincipal();

????????AuthenticationException?exception?=
????????????????new?AuthenticationServiceException(
????????????????????????String.format("[%s]?用戶在另外一臺(tái)電腦登錄,您已被下線",?userDetails.getUsername()));

????????try?{
????????????//?當(dāng)用戶在另外一臺(tái)電腦登錄后,交給失敗處理器回到認(rèn)證頁(yè)面
????????????event.getRequest().setAttribute("toAuthentication"?,?true);
????????????myAuthenticationFailureHandler
????????????????????.onAuthenticationFailure(event.getRequest(),?event.getResponse(),?exception);
????????}?catch?(ServletException?e)?{
????????????e.printStackTrace();
????????}
????}
}
  • 在com.security.learn.config.Myconfig類中注入SessionInformationExpiredStrategy
????@Bean
????@ConditionalOnMissingBean(SessionInformationExpiredStrategy.class)
????public?SessionInformationExpiredStrategy?informationExpiredStrategy(){
????????return?new?MySessionInformationExpiredStrategy();
????}

測(cè)試:

  1. 谷歌瀏覽器用戶名密碼登錄
  2. 再使用火狐瀏覽器用戶名密碼登錄
  3. 回到谷歌瀏覽器刷新請(qǐng)求乞封,發(fā)現(xiàn)回到登錄頁(yè)面,提示被下線

如果同一個(gè)用戶在第二個(gè)地方登錄岗憋,則不允許二次的登錄

如果同一用戶在第2個(gè)地方登錄時(shí)肃晚,則不允許他二次登錄。

實(shí)現(xiàn)步驟:

  1. 在 LearnSrpingSecurity添加 maxSessionsPreventsLogin(true) 后不允許二次登錄,其實(shí)就是添加一個(gè) .maxSessionsPreventsLogin(true)仔戈。
????????????????.and()
????????????????.sessionManagement()
????????????????.invalidSessionStrategy(invalidSessionStrategy)
????????????????.maximumSessions(1)////?每個(gè)用戶在系統(tǒng)中的最大session數(shù)
????????????????.maxSessionsPreventsLogin(true)
????????????????.expiredSessionStrategy(sessionInformationExpiredStrategy)//?當(dāng)用戶達(dá)到最大session數(shù)后关串,則調(diào)用此處的實(shí)現(xiàn)
????????????????;

測(cè)試:

  1. 谷歌瀏覽器用戶名密碼登錄
  2. 再使用火狐瀏覽器用戶名密碼登錄,發(fā)現(xiàn)不允許登錄

解決同一用戶的手機(jī)重復(fù)登錄問題

使用了 admin 用戶名登錄后监徘,還可以使用這個(gè)用戶的手機(jī)號(hào)登錄晋修。正常應(yīng)該是同一個(gè)用戶,系統(tǒng)中只能用用戶名或手機(jī)號(hào)登錄一次凰盔。

解決問題:

原因是 SmsCodeAuthenticationFilter繼承的類 AbstractAuthenticationProcessingFilter 中飞蚓,默認(rèn)使用了 NullAuthenticatedSessionStrategy 實(shí)例管理 Session,我們應(yīng)該指定用戶名密碼過濾器中所使用的那個(gè)SessionAuthenticationStrategy 實(shí)現(xiàn)類 CompositeSessionAuthenticationStrategy 廊蜒。在com.security.learn.config.SmsCodeSecurityConfig指定即可解決:

@Component
public?class?SmsCodeSecurityConfig?extends?SecurityConfigurerAdapter<DefaultSecurityFilterChain,?HttpSecurity>?{

????@Autowired
????@Qualifier("smsCodeUserDetailsService")
????private?SmsCodeUserDetailsService?smsCodeUserDetailsService;

????@Resource
????private?SmsCodeValidateFilter??smsCodeValidateFilter;

????@Override
????public?void?configure(HttpSecurity?http)?throws?Exception?{

????????//創(chuàng)建手機(jī)校驗(yàn)過濾器實(shí)例
????????SmsCodeAuthenticationFilter?smsCodeAuthenticationFilter?=?new?SmsCodeAuthenticationFilter();
????????//接收?AuthenticationManager?認(rèn)證管理器
????????smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
????????//處理成功handler
????????//smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
????????//處理失敗handler
????????//smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
????????smsCodeAuthenticationFilter.setRememberMeServices(http.getSharedObject(RememberMeServices.class));
????????smsCodeAuthenticationFilter.setSessionAuthenticationStrategy(http.getSharedObject(SessionAuthenticationStrategy.class));
????????//?獲取驗(yàn)證碼提供者
????????SmsCodeAuthenticationProvider?smsCodeAuthenticationProvider?=?new?SmsCodeAuthenticationProvider();
????????smsCodeAuthenticationProvider.setUserDetailsService(smsCodeUserDetailsService);

????????//在用戶密碼過濾器前面加入短信驗(yàn)證碼校驗(yàn)過濾器
????????http.addFilterBefore(smsCodeValidateFilter,?UsernamePasswordAuthenticationFilter.class);
????????//在用戶密碼過濾器后面加入短信驗(yàn)證碼認(rèn)證授權(quán)過濾器
????????http.authenticationProvider(smsCodeAuthenticationProvider)
????????????????.addFilterAfter(smsCodeAuthenticationFilter,?UsernamePasswordAuthenticationFilter.class);

????}
}

退出系統(tǒng)

解決退出不允許再次登錄

配置了 .maxSessionsPreventsLogin(true) 開啟了前面已登錄趴拧,不允許再重復(fù)登錄上面默認(rèn)情況,如果登錄后山叮,然后請(qǐng)求 /logout 退出著榴,再重新登錄時(shí),會(huì)提示不能重復(fù)登錄屁倔。

源碼分析

每次登錄請(qǐng)求都會(huì)執(zhí)行 ConcurrentSessionControlAuthenticationStrategy#onAuthentication判斷用戶在系統(tǒng)是否已經(jīng)存在 session了, 且判斷是否已經(jīng)超過限制的session數(shù)量了,超出則拋異常

原因是退出時(shí),并沒有將 SessionRegistryImpl.principals緩存用戶信息進(jìn)行刪除

@Override
?public?void?onAuthentication(Authentication?authentication,?HttpServletRequest?request,???HttpServletResponse?response)?{
??int?allowedSessions?=?getMaximumSessionsForThisUser(authentication);
??if?(allowedSessions?==?-1)?{
???//?We?permit?unlimited?logins
???return;
??}
??//?獲取當(dāng)前用戶在系統(tǒng)中的所有?session
??List<SessionInformation>?sessions?=?this.sessionRegistry.getAllSessions(authentication.getPrincipal(),?false);
??int?sessionCount?=?sessions.size();
??if?(sessionCount?<?allowedSessions)?{
???//?用戶session總個(gè)數(shù)?小于?設(shè)置的最大session數(shù)?,則直接通過
???return;
??}
????????//?創(chuàng)建session
??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?sessions
????for?(SessionInformation?si?:?sessions)?{
?????if?(si.getSessionId().equals(session.getId()))?{
??????return;
?????}
????}
???}
??
??}
????????//?超出允許的?session?的個(gè)數(shù)?,?maxSessionsPreventsLogin(true)拋出異常不讓登錄
??allowableSessionsExceeded(sessions,?allowedSessions,?this.sessionRegistry);
?}

解決方案

  • 在com.security.learn.config.Myconfig中注入SessionRegistry
????@Bean
????public?SessionRegistry?sessionRegistry()?{
????????return?new?SessionRegistryImpl();
????}
  • 添加一個(gè)退出處理器com.security.learn.handler.MyLogoutHandler ,將用戶信息從緩存中清

@Component
public?class?MyLogoutHandler?implements?LogoutHandler?{

????@Autowired
????private?SessionRegistry?sessionRegistry;

????@Override
????public?void?logout(HttpServletRequest?request,
???????????????????????HttpServletResponse?response,???????????????????????Authentication?authentication)?{
????????//?退出之后?脑又,將對(duì)應(yīng)session從緩存中清除?SessionRegistryImpl.principals
????????sessionRegistry.removeSessionInformation(request.getSession().getId());
????}
}

SpringSecurityConfifig 注入 customLogoutHandler

自定義退出處理

在 com.security.learn.config.LearnSrpingSecurity注入MyLogoutHandler

????/**
?????*?退出清除緩存?????*/
????@Autowired
????private?MyLogoutHandler?myLogoutHandler;
????@Autowired
????private?SessionRegistry?sessionRegistry;

????????????????.and().logout()
????????????????.addLogoutHandler(myLogoutHandler)
????????????????.logoutUrl("/logout")
????????????????.logoutSuccessUrl("/login/page")
????????????????.deleteCookies("JSESSIONID")

如果您覺得本文不錯(cuò),歡迎關(guān)注,點(diǎn)贊,收藏支持锐借,您的關(guān)注是我堅(jiān)持的動(dòng)力问麸!

原創(chuàng)不易,轉(zhuǎn)載請(qǐng)注明出處钞翔,感謝支持严卖!如果本文對(duì)您有用,歡迎轉(zhuǎn)發(fā)分享布轿!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哮笆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子汰扭,更是在濱河造成了極大的恐慌稠肘,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萝毛,死亡現(xiàn)場(chǎng)離奇詭異项阴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笆包,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門环揽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拷沸,“玉大人,你說我怎么就攤上這事薯演∽采郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵跨扮,是天一觀的道長(zhǎng)序无。 經(jīng)常有香客問我,道長(zhǎng)衡创,這世上最難降的妖魔是什么帝嗡? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮璃氢,結(jié)果婚禮上哟玷,老公的妹妹穿的比我還像新娘。我一直安慰自己一也,他們只是感情好巢寡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著椰苟,像睡著了一般抑月。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舆蝴,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天谦絮,我揣著相機(jī)與錄音,去河邊找鬼洁仗。 笑死层皱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赠潦。 我是一名探鬼主播叫胖,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼祭椰!你這毒婦竟也來了臭家?” 一聲冷哼從身側(cè)響起疲陕,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤方淤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蹄殃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體携茂,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年诅岩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讳苦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片带膜。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸳谜,靈堂內(nèi)的尸體忽然破棺而出膝藕,到底是詐尸還是另有隱情,我是刑警寧澤咐扭,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布芭挽,位于F島的核電站,受9級(jí)特大地震影響蝗肪,放射性物質(zhì)發(fā)生泄漏袜爪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一薛闪、第九天 我趴在偏房一處隱蔽的房頂上張望辛馆。 院中可真熱鬧,春花似錦豁延、人聲如沸昙篙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瓢对。三九已至,卻和暖如春胰苏,著一層夾襖步出監(jiān)牢的瞬間硕蛹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工硕并, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留法焰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓倔毙,卻偏偏與公主長(zhǎng)得像埃仪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陕赃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容