SpringSecurity整合springBoot、redis实现登录互踢功能
背景
基于我的文章——《SpringSecurity整合springBoot、redis token动态url权限校验》。要实现的功能是要实现一个用户不可以同时在两台设备上登录,有两种思路:
(1)后来的登录自动踢掉前面的登录。
(2)如果用户已经登录,则不允许后来者登录。
需要特别说明的是,项目的基础是已经是redis维护的session。
配置redisHttpSession
设置spring session由redis 管理。
2.1去掉yml中的http session 配置,yml和注解两者只选其一(同时配置,只有注解配置生效)。至于为什么不用yml,待会提到。
2.2 webSecurityConfig中加入注解@EnableRedisHttpSession
@EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 1700 , flushMode = FlushMode.ON_SAVE)
登录后发现redis session namespace已经是我们命名的了
获取redis管理的sessionRepository
我们要限制一个用户的登录,自然要获取他在系统中的所有session。
2.再去查看springsSession官网的文档。springsession官网 提供文档https://docs.spring.io/spring-session/docs/ 2.2.2.RELEASE/reference/html5/#api-findbyindexnamesessionrepository
SessionRepository实现也可以选择实现FindByIndexNameSessionRepository
FindByIndexNameSessionRepository提供一种方法,用于查找具有给定索引名称和索引值的所有会话
FindByIndexNameSessionRepository实现时,可以使用方便的方法查找特定用户的所有会话
/** * redis获取sessionRepository * RedisIndexedSessionRepository实现 FindByIndexNameSessionRepository接口 */ @Autowired //不加@Lazy这个会报什么循环引用... // Circular reference involving containing bean '.RedisHttpSessionConfiguration' @Lazy private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
这里注意一点,当我通过yml配置redis session是,sessionRepository下面会有红线。
虽然不影响运行,但是强迫症,所以改用@EnableWebSecurity注解(至于为什么?我也不想知道…)。
将sessionRepository注入SpringSessionBackedSessionRegistry
是spring session为Spring Security提供的什么会话并发的会话注册表实现,大概是让springSecurity帮我们去限制登录,光一个sessionRepository是不行的,还得自己加点工具什么的。
webSecurityConfig加入:
/** * 是spring session为Spring Security提供的, * 用于在集群环境下控制会话并发的会话注册表实现 * @return */ @Bean public SpringSessionBackedSessionRegistry sessionRegistry(){ return new SpringSessionBackedSessionRegistry<>(sessionRepository); }
注:
https://blog.csdn.net/qq_34136709/article/details/106012825 这篇文章说还需要加一个HttpSessionEventPublisher来监听session销毁云云,大概是因为我用的是redis session吧,不需要这个,要了之后还会报错,啥错?我忘了。
新增一个session过期后的处理类
先创建一个CustomSessionInformationExpiredStrategy.java来处理session过期后如何通知前端的处理类,内容如下:
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); } }
注:一般都是自己重新写返回前端的信息,不会直接用框架抛出的错误信息
配置到configure(HttpSecurity http)方法上
.csrf().disable() //登录互踢 .sessionManagement() //在这里设置session的认证策略无效 //.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 这篇文章说session认证的原理,我看到它是执行了一个session的认证策略,但是我debug对应的代码时,发现
这个session认证策略是NullAuthenticatedSessionStrategy,而不是它说的ConcurrentSessionControlAuthenticationStrategy。就是说我需要在哪里去配置这个session 认证策略。第一时间想到了configure(HttpSecurity http)里面配置
结果无效。之后看到别人的代码,想到这个策略应该是要在登录的时候加上去,而我们的登录一般都需要自己重写,自然上面的写法会无效。于是我找到了自定义的登录过滤器。
然后发现this.setSessionAuthenticationStrategy(sessionStrategy);确实存在。
public LoginFilter(UserVerifyAuthenticationProvider authenticationManager, CustomAuthenticationSuccessHandler successHandler, CustomAuthenticationFailureHandler failureHandler, SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry) { //设置认证管理器(对登录请求进行认证和授权) this.authenticationManager = authenticationManager; //设置认证成功后的处理类 this.setAuthenticationSuccessHandler(successHandler); //设置认证失败后的处理类 this.setAuthenticationFailureHandler(failureHandler); //配置session认证策略(将springSecurity包装redis Session作为参数传入) ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(springSessionBackedSessionRegistry); //最多允许一个session sessionStrategy.setMaximumSessions(1); this.setSessionAuthenticationStrategy(sessionStrategy); //可以自定义登录请求的url super.setFilterProcessesUrl("/myLogin"); }
启动 后就发现session认证策略已经改为我们设定的策略了。
完整的webSecurityConfig如下:
@Configuration @EnableWebSecurity //RedisFlushMode有两个参数:ON_SAVE(表示在response commit前刷新缓存),IMMEDIATE(表示只要有更新,就刷新缓存) //yml和注解两者只选其一(同时配置,只有注解配置生效) @EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 5000 , flushMode = FlushMode.ON_SAVE) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserVerifyAuthenticationProvider authenticationManager;//认证用户类 @Autowired private CustomAuthenticationSuccessHandler successHandler;//登录认证成功处理类 @Autowired private CustomAuthenticationFailureHandler failureHandler;//登录认证失败处理类 @Autowired private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回当前URL允许访问的角色列表 @Autowired private MyAccessDecisionManager accessDecisionManager;//除登录登出外所有接口的权限校验 /** * redis获取sessionRepository * RedisIndexedSessionRepository实现 FindByIndexNameSessionRepository接口 */ @Autowired //不加@Lazy这个会报什么循环引用... // Circular reference involving containing bean '.RedisHttpSessionConfiguration' @Lazy private FindByIndexNameSessionRepository<? extends Session> sessionRepository; /** * 是spring session为Spring Security提供的, * 用于在集群环境下控制会话并发的会话注册表实现 * @return */ @Bean public SpringSessionBackedSessionRegistry sessionRegistry(){ return new SpringSessionBackedSessionRegistry<>(sessionRepository); } /** * 密码加密 * @return */ @Bean @ConditionalOnMissingBean(PasswordEncoder.class) public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 配置 HttpSessionIdResolver Bean * 登录之后将会在 Response Header x-auth-token 中 返回当前 sessionToken * 将token存储在前端 每次调用的时候 Request Header x-auth-token 带上 sessionToken */ @Bean public HttpSessionIdResolver httpSessionIdResolver() { return HeaderHttpSessionIdResolver.xAuthToken(); } /** * Swagger等静态资源不进行拦截 */ @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只需要用户进行身份验证 .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() //用来解决匿名用户访问无权限资源时的异常 .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint()) //用来解决登陆认证过的用户访问无权限资源时的异常 .accessDeniedHandler(new CustomAccessDeniedHandler()) .and() //配置登录过滤器 .addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler, sessionRegistry())) .csrf().disable() //登录互踢 .sessionManagement() //在这里设置session的认证策略无效 //.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() //禁用页面镶嵌frame劫持安全协议 // 防止iframe 造成跨域 .frameOptions().disable(); } }
其他
@Lazy private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
至于这个不加@lazy会什么循环引用的问题,我就真的不想理会了。看了好长时间,都不知道谁和谁发生了循环引用。。。。。
到此这篇关于SpringSecurity整合springBoot、redis——实现登录互踢的文章就介绍到这了,更多相关SpringSecurity登录互踢内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
相关文章
- 这篇文章主要介绍了Spring AOP 对象内部方法间的嵌套调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-29
Spring Cloud 中@FeignClient注解中的contextId属性详解
这篇文章主要介绍了Spring Cloud 中@FeignClient注解中的contextId属性详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-25- 在很多网站用户先访问一个要登录的页面,但当时没有登录后来登录了,等待用户登录成功之后肯定希望返回到上次访问的页面,下面我就来给大家介绍登录后跳转回原来要访问的页...2016-11-25
Springboot如何实现Web系统License授权认证
这篇文章主要介绍了Springboot如何实现Web系统License授权认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-28- 本文章完美的利用了php的curl功能实现模拟登录discuz以及模拟发帖,本教程供参考学习哦。 代码如下 复制代码 <?php $discuz_url = ‘ht...2016-11-25
如何在Spring WebFlux的任何地方获取Request对象
这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26- 这篇文章主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-16
Ruby on Rails实现最基本的用户注册和登录功能的教程
这里我们主要以has_secure_password的用户密码验证功能为中心,来讲解Ruby on Rails实现最基本的用户注册和登录功能的教程,需要的朋友可以参考下...2020-06-30- @Autowired 注解的主要功能就是完成自动注入,使用也非常简单,但这篇文章主要给大家介绍了关于Spring为什么不推荐使用@Autowired注解的相关资料,需要的朋友可以参考下...2021-11-03
- 什么是SSO?单点登录SSO(Single Sign-On)是身份管理中的一部分。SSO的一种较为通俗的定义是:SSO是指访问同一服务器不同应用中的受保护资源的同一用户,只需要登录一次,即通过一个应用中的安全验证后,再访问其他应用中的受保护...2015-11-08
- 【问题描述】:同一用户在同一时间多次登录如果不能检测出来,是危险的。因为,你无法知道是否有其他用户在登录你的账户。如何禁止同一用户多次登录呢? 【解决方案】 (1) 每次登录,身份认证成功后,重新产生一个session_id。 s...2015-11-24
- 什么是SSO?单点登录SSO(Single Sign-On)是身份管理中的一部分。SSO的一种较为通俗的定义是:SSO是指访问同一服务器不同应用中的受保护资源的同一用户,只需要登录一次,即通过一个应用中的安全验证后,再访问其他应用中的受保护...2015-11-08
Springboot如何使用mybatis实现拦截SQL分页
这篇文章主要介绍了Springboot使用mybatis实现拦截SQL分页,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-19- 这篇文章主要为大家详细介绍了vue实现用户登录切换,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-04-22
- 这篇文章主要介绍了SpringMVC文件上传原理及实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-15
Spring Data JPA 关键字Exists的用法说明
这篇文章主要介绍了Spring Data JPA 关键字Exists的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-10- 出现phpmyadmin不能登录是我在修改我mysql服务器密码之后导致的,后来百度了相关的原因,原来是修改了mysql密码之后我们还需要在phpmyadmin目录中去修改config.inc.php中...2016-11-25
SpringBoot2.x中management.security.enabled=false无效的解决
这篇文章主要介绍了SpringBoot2.x中management.security.enabled=false无效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-07-23Vue-Element-Admin集成自己的接口实现登录跳转
关于这个Vue-element-admin中的流程可能对于新的同学不是很友好,所以本文将结合实例代码,介绍Vue-Element-Admin集成自己的接口实现登录跳转,感兴趣的小伙伴们可以参考一下...2021-06-23- 这篇文章主要介绍了浅谈js二维码扫码登录是什么原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-13