SpringSecurity-11-只允許一個(gè)用戶登錄
本次給你介紹只允許用戶在一個(gè)地方登錄止潘,也就是說每個(gè)用戶只允許有一個(gè)Session。他有兩種場(chǎng)景
- 如果同一個(gè)用戶在第二個(gè)地方登錄幌甘,則將第一個(gè)登錄下線
- 如果同一個(gè)用戶在第二個(gè)地方登錄,則不允許二次的登錄
同一個(gè)用戶在第二個(gè)地方登錄,則將第一個(gè)登錄退出
具體步驟如下:
- 重構(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è)試:
- 谷歌瀏覽器用戶名密碼登錄
- 再使用火狐瀏覽器用戶名密碼登錄
- 回到谷歌瀏覽器刷新請(qǐng)求乞封,發(fā)現(xiàn)回到登錄頁(yè)面,提示被下線
如果同一個(gè)用戶在第二個(gè)地方登錄岗憋,則不允許二次的登錄
如果同一用戶在第2個(gè)地方登錄時(shí)肃晚,則不允許他二次登錄。
實(shí)現(xiàn)步驟:
- 在 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è)試:
- 谷歌瀏覽器用戶名密碼登錄
- 再使用火狐瀏覽器用戶名密碼登錄,發(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ā)分享布轿!