SpringSecurity整合springBoot、redis實(shí)現(xiàn)登錄互踢功能
背景
基于我的文章——《SpringSecurity整合springBoot、redis token動(dòng)態(tài)url權(quán)限校驗(yàn)》。要實(shí)現(xiàn)的功能是要實(shí)現(xiàn)一個(gè)用戶不可以同時(shí)在兩臺(tái)設(shè)備上登錄,有兩種思路:
(1)后來(lái)的登錄自動(dòng)踢掉前面的登錄。
(2)如果用戶已經(jīng)登錄,則不允許后來(lái)者登錄。
需要特別說(shuō)明的是,項(xiàng)目的基礎(chǔ)是已經(jīng)是redis維護(hù)的session。
配置redisHttpSession
設(shè)置spring session由redis 管理。
2.1去掉yml中的http session 配置,yml和注解兩者只選其一(同時(shí)配置,只有注解配置生效)。至于為什么不用yml,待會(huì)提到。

2.2 webSecurityConfig中加入注解@EnableRedisHttpSession

@EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 1700
, flushMode = FlushMode.ON_SAVE)
登錄后發(fā)現(xiàn)redis session namespace已經(jīng)是我們命名的了

獲取redis管理的sessionRepository
我們要限制一個(gè)用戶的登錄,自然要獲取他在系統(tǒng)中的所有session。
2.再去查看springsSession官網(wǎng)的文檔。springsession官網(wǎng) 提供文檔https://docs.spring.io/spring-session/docs/ 2.2.2.RELEASE/reference/html5/#api-findbyindexnamesessionrepository
SessionRepository實(shí)現(xiàn)也可以選擇實(shí)現(xiàn)FindByIndexNameSessionRepository
FindByIndexNameSessionRepository提供一種方法,用于查找具有給定索引名稱和索引值的所有會(huì)話
FindByIndexNameSessionRepository實(shí)現(xiàn)時(shí),可以使用方便的方法查找特定用戶的所有會(huì)話
/**
* redis獲取sessionRepository
* RedisIndexedSessionRepository實(shí)現(xiàn) FindByIndexNameSessionRepository接口
*/
@Autowired
//不加@Lazy這個(gè)會(huì)報(bào)什么循環(huán)引用...
// Circular reference involving containing bean '.RedisHttpSessionConfiguration'
@Lazy
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
這里注意一點(diǎn),當(dāng)我通過(guò)yml配置redis session是,sessionRepository下面會(huì)有紅線。

雖然不影響運(yùn)行,但是強(qiáng)迫癥,所以改用@EnableWebSecurity注解(至于為什么?我也不想知道…)。
將sessionRepository注入SpringSessionBackedSessionRegistry
是spring session為Spring Security提供的什么會(huì)話并發(fā)的會(huì)話注冊(cè)表實(shí)現(xiàn),大概是讓springSecurity幫我們?nèi)ハ拗频卿?,光一個(gè)sessionRepository是不行的,還得自己加點(diǎn)工具什么的。
webSecurityConfig加入:
/**
* 是spring session為Spring Security提供的,
* 用于在集群環(huán)境下控制會(huì)話并發(fā)的會(huì)話注冊(cè)表實(shí)現(xiàn)
* @return
*/
@Bean
public SpringSessionBackedSessionRegistry sessionRegistry(){
return new SpringSessionBackedSessionRegistry<>(sessionRepository);
}
注:
https://blog.csdn.net/qq_34136709/article/details/106012825 這篇文章說(shuō)還需要加一個(gè)HttpSessionEventPublisher來(lái)監(jiān)聽(tīng)session銷毀云云,大概是因?yàn)槲矣玫氖莚edis session吧,不需要這個(gè),要了之后還會(huì)報(bào)錯(cuò),啥錯(cuò)?我忘了。
新增一個(gè)session過(guò)期后的處理類
先創(chuàng)建一個(gè)CustomSessionInformationExpiredStrategy.java來(lái)處理session過(guò)期后如何通知前端的處理類,內(nèi)容如下:
public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
if (log.isDebugEnabled()) {
log.debug("{} {}", event.getSessionInformation(), MessageConstant.SESSION_EVICT);
}
HttpServletResponse response = event.getResponse();
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
String responseJson = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.SESSION_EVICT, MessageConstant.SESSION_EVICT));
response.getWriter().write(responseJson);
}
}
注:一般都是自己重新寫(xiě)返回前端的信息,不會(huì)直接用框架拋出的錯(cuò)誤信息
配置到configure(HttpSecurity http)方法上
.csrf().disable() //登錄互踢 .sessionManagement() //在這里設(shè)置session的認(rèn)證策略無(wú)效 //.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry())) .maximumSessions(1) .sessionRegistry(sessionRegistry()) .maxSessionsPreventsLogin(false) //false表示不阻止登錄,就是新的覆蓋舊的 //session失效后要做什么(提示前端什么內(nèi)容) .expiredSessionStrategy(new CustomSessionInformationExpiredStrategy());
注意:https://blog.csdn.net/qq_34136709/article/details/106012825 這篇文章說(shuō)session認(rèn)證的原理,我看到它是執(zhí)行了一個(gè)session的認(rèn)證策略,但是我debug對(duì)應(yīng)的代碼時(shí),發(fā)現(xiàn)

這個(gè)session認(rèn)證策略是NullAuthenticatedSessionStrategy,而不是它說(shuō)的ConcurrentSessionControlAuthenticationStrategy。就是說(shuō)我需要在哪里去配置這個(gè)session 認(rèn)證策略。第一時(shí)間想到了configure(HttpSecurity http)里面配置

結(jié)果無(wú)效。之后看到別人的代碼,想到這個(gè)策略應(yīng)該是要在登錄的時(shí)候加上去,而我們的登錄一般都需要自己重寫(xiě),自然上面的寫(xiě)法會(huì)無(wú)效。于是我找到了自定義的登錄過(guò)濾器。


然后發(fā)現(xiàn)this.setSessionAuthenticationStrategy(sessionStrategy);確實(shí)存在。
public LoginFilter(UserVerifyAuthenticationProvider authenticationManager,
CustomAuthenticationSuccessHandler successHandler,
CustomAuthenticationFailureHandler failureHandler,
SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry) {
//設(shè)置認(rèn)證管理器(對(duì)登錄請(qǐng)求進(jìn)行認(rèn)證和授權(quán))
this.authenticationManager = authenticationManager;
//設(shè)置認(rèn)證成功后的處理類
this.setAuthenticationSuccessHandler(successHandler);
//設(shè)置認(rèn)證失敗后的處理類
this.setAuthenticationFailureHandler(failureHandler);
//配置session認(rèn)證策略(將springSecurity包裝redis Session作為參數(shù)傳入)
ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new
ConcurrentSessionControlAuthenticationStrategy(springSessionBackedSessionRegistry);
//最多允許一個(gè)session
sessionStrategy.setMaximumSessions(1);
this.setSessionAuthenticationStrategy(sessionStrategy);
//可以自定義登錄請(qǐng)求的url
super.setFilterProcessesUrl("/myLogin");
}
啟動(dòng) 后就發(fā)現(xiàn)session認(rèn)證策略已經(jīng)改為我們?cè)O(shè)定的策略了。
完整的webSecurityConfig如下:
@Configuration
@EnableWebSecurity
//RedisFlushMode有兩個(gè)參數(shù):ON_SAVE(表示在response commit前刷新緩存),IMMEDIATE(表示只要有更新,就刷新緩存)
//yml和注解兩者只選其一(同時(shí)配置,只有注解配置生效)
@EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 5000
, flushMode = FlushMode.ON_SAVE)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserVerifyAuthenticationProvider authenticationManager;//認(rèn)證用戶類
@Autowired
private CustomAuthenticationSuccessHandler successHandler;//登錄認(rèn)證成功處理類
@Autowired
private CustomAuthenticationFailureHandler failureHandler;//登錄認(rèn)證失敗處理類
@Autowired
private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回當(dāng)前URL允許訪問(wèn)的角色列表
@Autowired
private MyAccessDecisionManager accessDecisionManager;//除登錄登出外所有接口的權(quán)限校驗(yàn)
/**
* redis獲取sessionRepository
* RedisIndexedSessionRepository實(shí)現(xiàn) FindByIndexNameSessionRepository接口
*/
@Autowired
//不加@Lazy這個(gè)會(huì)報(bào)什么循環(huán)引用...
// Circular reference involving containing bean '.RedisHttpSessionConfiguration'
@Lazy
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
/**
* 是spring session為Spring Security提供的,
* 用于在集群環(huán)境下控制會(huì)話并發(fā)的會(huì)話注冊(cè)表實(shí)現(xiàn)
* @return
*/
@Bean
public SpringSessionBackedSessionRegistry sessionRegistry(){
return new SpringSessionBackedSessionRegistry<>(sessionRepository);
}
/**
* 密碼加密
* @return
*/
@Bean
@ConditionalOnMissingBean(PasswordEncoder.class)
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置 HttpSessionIdResolver Bean
* 登錄之后將會(huì)在 Response Header x-auth-token 中 返回當(dāng)前 sessionToken
* 將token存儲(chǔ)在前端 每次調(diào)用的時(shí)候 Request Header x-auth-token 帶上 sessionToken
*/
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
/**
* Swagger等靜態(tài)資源不進(jìn)行攔截
*/
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/error",
"/webjars/**",
"/resources/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v2/api-docs");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//配置一些不需要登錄就可以訪問(wèn)的接口,這里配置失效了,放到了securityMetadataSource里面
//.antMatchers("/demo/**", "/about/**").permitAll()
//任何尚未匹配的URL只需要用戶進(jìn)行身份驗(yàn)證
.anyRequest().authenticated()
//登錄后的接口權(quán)限校驗(yàn)
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(accessDecisionManager);
object.setSecurityMetadataSource(securityMetadataSource);
return object;
}
})
.and()
//配置登出處理
.logout().logoutUrl("/logout")
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.clearAuthentication(true)
.and()
//用來(lái)解決匿名用戶訪問(wèn)無(wú)權(quán)限資源時(shí)的異常
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
//用來(lái)解決登陸認(rèn)證過(guò)的用戶訪問(wèn)無(wú)權(quán)限資源時(shí)的異常
.accessDeniedHandler(new CustomAccessDeniedHandler())
.and()
//配置登錄過(guò)濾器
.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler, sessionRegistry()))
.csrf().disable()
//登錄互踢
.sessionManagement()
//在這里設(shè)置session的認(rèn)證策略無(wú)效
//.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry()))
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(false) //false表示不阻止登錄,就是新的覆蓋舊的
//session失效后要做什么(提示前端什么內(nèi)容)
.expiredSessionStrategy(new CustomSessionInformationExpiredStrategy());
//配置頭部
http.headers()
.contentTypeOptions()
.and()
.xssProtection()
.and()
//禁用緩存
.cacheControl()
.and()
.httpStrictTransportSecurity()
.and()
//禁用頁(yè)面鑲嵌frame劫持安全協(xié)議 // 防止iframe 造成跨域
.frameOptions().disable();
}
}
其他
@Lazy private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
至于這個(gè)不加@lazy會(huì)什么循環(huán)引用的問(wèn)題,我就真的不想理會(huì)了??戳撕瞄L(zhǎng)時(shí)間,都不知道誰(shuí)和誰(shuí)發(fā)生了循環(huán)引用。。。。。
到此這篇關(guān)于SpringSecurity整合springBoot、redis——實(shí)現(xiàn)登錄互踢的文章就介紹到這了,更多相關(guān)SpringSecurity登錄互踢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot如何整合Springsecurity實(shí)現(xiàn)數(shù)據(jù)庫(kù)登錄及權(quán)限控制
- SpringBoot基于SpringSecurity表單登錄和權(quán)限驗(yàn)證的示例
- Springboot整合SpringSecurity實(shí)現(xiàn)登錄認(rèn)證和鑒權(quán)全過(guò)程
- Springboot+SpringSecurity實(shí)現(xiàn)圖片驗(yàn)證碼登錄的示例
- springboot+jwt+springSecurity微信小程序授權(quán)登錄問(wèn)題
- SpringBoot 配合 SpringSecurity 實(shí)現(xiàn)自動(dòng)登錄功能的代碼
- SpringBoot + SpringSecurity 短信驗(yàn)證碼登錄功能實(shí)現(xiàn)
- SpringBoot+SpringSecurity處理Ajax登錄請(qǐng)求問(wèn)題(推薦)
- SpringBoot整合Springsecurity實(shí)現(xiàn)數(shù)據(jù)庫(kù)登錄及權(quán)限控制功能
相關(guān)文章
SpringBoot項(xiàng)目如何將Bean注入到普通類中
這篇文章主要介紹了SpringBoot項(xiàng)目如何將Bean注入到普通類中,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
淺談Java開(kāi)發(fā)中的安全編碼問(wèn)題
這篇文章主要介紹了淺談Java開(kāi)發(fā)中的安全編碼問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
Java如何使用Set接口存儲(chǔ)沒(méi)有重復(fù)元素的數(shù)組
Set是一個(gè)繼承于Collection的接口,即Set也是集合中的一種。Set是沒(méi)有重復(fù)元素的集合,本篇我們就用它存儲(chǔ)一個(gè)沒(méi)有重復(fù)元素的數(shù)組2022-04-04
Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析
這篇文章主要介紹了Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12
簡(jiǎn)單了解java ibatis #及$的區(qū)別和用法
這篇文章主要介紹了簡(jiǎn)單了解java ibatis #及$的區(qū)別和用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Spring Boot 簡(jiǎn)單使用EhCache緩存框架的方法
本篇文章主要介紹了Spring Boot 簡(jiǎn)單使用EhCache緩存框架的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07

