- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- SpringSecurity整合springBoot、redis實(shí)現登錄互踢功能
基于我的文章——《》。要實(shí)現的功能是要實(shí)現一個(gè)用戶(hù)不可以同時(shí)在兩臺設備上登錄,有兩種思路:
(1)后來(lái)的登錄自動(dòng)踢掉前面的登錄。
(2)如果用戶(hù)已經(jīng)登錄,則不允許后來(lái)者登錄。
需要特別說(shuō)明的是,項目的基礎是已經(jīng)是redis維護的session。
設置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ā)現redis session namespace已經(jīng)是我們命名的了
我們要限制一個(gè)用戶(hù)的登錄,自然要獲取他在系統中的所有session。
2.再去查看springsSession官網(wǎng)的文檔。springsession官網(wǎng) 提供文檔 2.2.2.RELEASE/reference/html5/#api-findbyindexnamesessionrepository
SessionRepository實(shí)現也可以選擇實(shí)現FindByIndexNameSessionRepository
FindByIndexNameSessionRepository提供一種方法,用于查找具有給定索引名稱(chēng)和索引值的所有會(huì )話(huà)
FindByIndexNameSessionRepository實(shí)現時(shí),可以使用方便的方法查找特定用戶(hù)的所有會(huì )話(huà)
/** * redis獲取sessionRepository * RedisIndexedSessionRepository實(shí)現 FindByIndexNameSessionRepository接口 */ @Autowired //不加@Lazy這個(gè)會(huì )報什么循環(huán)引用... // Circular reference involving containing bean '.RedisHttpSessionConfiguration' @Lazy private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
這里注意一點(diǎn),當我通過(guò)yml配置redis session是,sessionRepository下面會(huì )有紅線(xiàn)。
雖然不影響運行,但是強迫癥,所以改用@EnableWebSecurity注解(至于為什么?我也不想知道…)。
是spring session為Spring Security提供的什么會(huì )話(huà)并發(fā)的會(huì )話(huà)注冊表實(shí)現,大概是讓springSecurity幫我們去限制登錄,光一個(gè)sessionRepository是不行的,還得自己加點(diǎn)工具什么的。
webSecurityConfig加入:
/** * 是spring session為Spring Security提供的, * 用于在集群環(huán)境下控制會(huì )話(huà)并發(fā)的會(huì )話(huà)注冊表實(shí)現 * @return */ @Bean public SpringSessionBackedSessionRegistry sessionRegistry(){ return new SpringSessionBackedSessionRegistry<>(sessionRepository); }
注:
https://blog.csdn.net/qq_34136709/article/details/106012825 這篇文章說(shuō)還需要加一個(gè)HttpSessionEventPublisher來(lái)監聽(tīng)session銷(xiāo)毀云云,大概是因為我用的是redis session吧,不需要這個(gè),要了之后還會(huì )報錯,啥錯?我忘了。
先創(chuàng )建一個(gè)CustomSessionInformationExpiredStrategy.java來(lái)處理session過(guò)期后如何通知前端的處理類(lè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ì )直接用框架拋出的錯誤信息
.csrf().disable() //登錄互踢 .sessionManagement() //在這里設置session的認證策略無(wú)效 //.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry())) .maximumSessions(1) .sessionRegistry(sessionRegistry()) .maxSessionsPreventsLogin(false) //false表示不阻止登錄,就是新的覆蓋舊的 //session失效后要做什么(提示前端什么內容) .expiredSessionStrategy(new CustomSessionInformationExpiredStrategy());
注意:https://blog.csdn.net/qq_34136709/article/details/106012825 這篇文章說(shuō)session認證的原理,我看到它是執行了一個(gè)session的認證策略,但是我debug對應的代碼時(shí),發(fā)現
這個(gè)session認證策略是NullAuthenticatedSessionStrategy,而不是它說(shuō)的ConcurrentSessionControlAuthenticationStrategy。就是說(shuō)我需要在哪里去配置這個(gè)session 認證策略。第一時(shí)間想到了configure(HttpSecurity http)里面配置
結果無(wú)效。之后看到別人的代碼,想到這個(gè)策略應該是要在登錄的時(shí)候加上去,而我們的登錄一般都需要自己重寫(xiě),自然上面的寫(xiě)法會(huì )無(wú)效。于是我找到了自定義的登錄過(guò)濾器。
然后發(fā)現this.setSessionAuthenticationStrategy(sessionStrategy);確實(shí)存在。
public LoginFilter(UserVerifyAuthenticationProvider authenticationManager, CustomAuthenticationSuccessHandler successHandler, CustomAuthenticationFailureHandler failureHandler, SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry) { //設置認證管理器(對登錄請求進(jìn)行認證和授權) this.authenticationManager = authenticationManager; //設置認證成功后的處理類(lèi) this.setAuthenticationSuccessHandler(successHandler); //設置認證失敗后的處理類(lèi) this.setAuthenticationFailureHandler(failureHandler); //配置session認證策略(將springSecurity包裝redis Session作為參數傳入) ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(springSessionBackedSessionRegistry); //最多允許一個(gè)session sessionStrategy.setMaximumSessions(1); this.setSessionAuthenticationStrategy(sessionStrategy); //可以自定義登錄請求的url super.setFilterProcessesUrl("/myLogin"); }
啟動(dòng) 后就發(fā)現session認證策略已經(jīng)改為我們設定的策略了。
完整的webSecurityConfig如下:
@Configuration @EnableWebSecurity //RedisFlushMode有兩個(gè)參數: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;//認證用戶(hù)類(lèi) @Autowired private CustomAuthenticationSuccessHandler successHandler;//登錄認證成功處理類(lèi) @Autowired private CustomAuthenticationFailureHandler failureHandler;//登錄認證失敗處理類(lèi) @Autowired private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回當前URL允許訪(fǎng)問(wèn)的角色列表 @Autowired private MyAccessDecisionManager accessDecisionManager;//除登錄登出外所有接口的權限校驗 /** * redis獲取sessionRepository * RedisIndexedSessionRepository實(shí)現 FindByIndexNameSessionRepository接口 */ @Autowired //不加@Lazy這個(gè)會(huì )報什么循環(huán)引用... // Circular reference involving containing bean '.RedisHttpSessionConfiguration' @Lazy private FindByIndexNameSessionRepository<? extends Session> sessionRepository; /** * 是spring session為Spring Security提供的, * 用于在集群環(huán)境下控制會(huì )話(huà)并發(fā)的會(huì )話(huà)注冊表實(shí)現 * @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 中 返回當前 sessionToken * 將token存儲在前端 每次調用的時(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() //配置一些不需要登錄就可以訪(fǎng)問(wèn)的接口,這里配置失效了,放到了securityMetadataSource里面 //.antMatchers("/demo/**", "/about/**").permitAll() //任何尚未匹配的URL只需要用戶(hù)進(jìn)行身份驗證 .anyRequest().authenticated() //登錄后的接口權限校驗 .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)解決匿名用戶(hù)訪(fǎng)問(wèn)無(wú)權限資源時(shí)的異常 .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint()) //用來(lái)解決登陸認證過(guò)的用戶(hù)訪(fǎng)問(wèn)無(wú)權限資源時(shí)的異常 .accessDeniedHandler(new CustomAccessDeniedHandler()) .and() //配置登錄過(guò)濾器 .addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler, sessionRegistry())) .csrf().disable() //登錄互踢 .sessionManagement() //在這里設置session的認證策略無(wú)效 //.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry())) .maximumSessions(1) .sessionRegistry(sessionRegistry()) .maxSessionsPreventsLogin(false) //false表示不阻止登錄,就是新的覆蓋舊的 //session失效后要做什么(提示前端什么內容) .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(cháng)時(shí)間,都不知道誰(shuí)和誰(shuí)發(fā)生了循環(huán)引用。。。。。
到此這篇關(guān)于SpringSecurity整合springBoot、redis——實(shí)現登錄互踢的文章就介紹到這了,更多相關(guān)SpringSecurity登錄互踢內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站