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)后來的登錄自動(dòng)踢掉前面的登錄。(2)如果用戶已經(jīng)登錄,則不允許后來者登錄。需要特別說明的是,項(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)是我們命名的了
我們要限制一個(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)我通過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 這篇文章說還需要加一個(gè)HttpSessionEventPublisher來監(jiān)聽session銷毀云云,大概是因?yàn)槲矣玫氖莚edis session吧,不需要這個(gè),要了之后還會(huì)報(bào)錯(cuò),啥錯(cuò)?我忘了。
新增一個(gè)session過期后的處理類先創(chuàng)建一個(gè)CustomSessionInformationExpiredStrategy.java來處理session過期后如何通知前端的處理類,內(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); }}
注:一般都是自己重新寫返回前端的信息,不會(huì)直接用框架拋出的錯(cuò)誤信息
配置到configure(HttpSecurity http)方法上.csrf().disable()//登錄互踢.sessionManagement()//在這里設(shè)置session的認(rèn)證策略無效//.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 這篇文章說session認(rèn)證的原理,我看到它是執(zhí)行了一個(gè)session的認(rèn)證策略,但是我debug對(duì)應(yīng)的代碼時(shí),發(fā)現(xiàn)
這個(gè)session認(rèn)證策略是NullAuthenticatedSessionStrategy,而不是它說的ConcurrentSessionControlAuthenticationStrategy。就是說我需要在哪里去配置這個(gè)session 認(rèn)證策略。第一時(shí)間想到了configure(HttpSecurity http)里面配置
結(jié)果無效。之后看到別人的代碼,想到這個(gè)策略應(yīng)該是要在登錄的時(shí)候加上去,而我們的登錄一般都需要自己重寫,自然上面的寫法會(huì)無效。于是我找到了自定義的登錄過濾器。
然后發(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 = newConcurrentSessionControlAuthenticationStrategy(springSessionBackedSessionRegistry);//最多允許一個(gè)sessionsessionStrategy.setMaximumSessions(1);this.setSessionAuthenticationStrategy(sessionStrategy);//可以自定義登錄請(qǐng)求的urlsuper.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允許訪問的角色列表 @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()//配置一些不需要登錄就可以訪問的接口,這里配置失效了,放到了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()//用來解決匿名用戶訪問無權(quán)限資源時(shí)的異常.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())//用來解決登陸認(rèn)證過的用戶訪問無權(quán)限資源時(shí)的異常.accessDeniedHandler(new CustomAccessDeniedHandler()).and()//配置登錄過濾器.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler, sessionRegistry())).csrf().disable()//登錄互踢.sessionManagement()//在這里設(shè)置session的認(rèn)證策略無效//.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()//禁用頁面鑲嵌frame劫持安全協(xié)議 // 防止iframe 造成跨域.frameOptions().disable(); }}
其他
@Lazyprivate FindByIndexNameSessionRepository<? extends Session> sessionRepository;
至于這個(gè)不加@lazy會(huì)什么循環(huán)引用的問題,我就真的不想理會(huì)了??戳撕瞄L(zhǎng)時(shí)間,都不知道誰和誰發(fā)生了循環(huán)引用。。。。。
到此這篇關(guān)于SpringSecurity整合springBoot、redis——實(shí)現(xiàn)登錄互踢的文章就介紹到這了,更多相關(guān)SpringSecurity登錄互踢內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. Python調(diào)用接口合并Excel表代碼實(shí)例2. ajax動(dòng)態(tài)加載json數(shù)據(jù)并詳細(xì)解析3. ASP.NET MVC使用Boostrap實(shí)現(xiàn)產(chǎn)品展示、查詢、排序、分頁4. 一文透徹詳解.NET框架類型系統(tǒng)設(shè)計(jì)要點(diǎn)5. Python快速將ppt制作成配音視頻課件的操作方法6. .net如何優(yōu)雅的使用EFCore實(shí)例詳解7. ASP.NET MVC實(shí)現(xiàn)橫向展示購物車8. ASP.Net Core對(duì)USB攝像頭進(jìn)行截圖9. 通過Ajax方式綁定select選項(xiàng)數(shù)據(jù)的實(shí)例10. 通過CSS數(shù)學(xué)函數(shù)實(shí)現(xiàn)動(dòng)畫特效
