什么是Session?我们都知道Session是服务器端记录用户状态的机制,用户是否登录、是否被挤兑、是否限制登录、登录状态是否过期等等都是关于它的事,那么Spring Security中是如何管理Session的呢,一起来了解下吧。
Session超时处理
问题描述
- 不同的项目对于登录超时时间是不同的,比如有的七天或者一个月不操作,后台会把登录会话设为过期,有的甚至一个小时不操作,就会把登录会话设为过期,比如说一些金融APP,这么做也是为了提高安全性。所以这里要处理两个问题,一个是需要知道
如何控制超时时间
,另一个是会话过期之后,如果再进行一个操作,该如何处理
?
设置超时时间
- 只需要在application.properties文件加上这么一句
spring.session.timeout = 10
单位是秒,不设置默认是30分钟
,这里设置了10秒,所以讲道理10秒不调用接口之后应该会需要重新登录了,但是事实上10秒之后session并没有失效,是什么原因呢?来看一段源码
private long getSessionTimeoutInMinutes() {
long sessionTimeout = (long)this.getSessionTimeout();
if (sessionTimeout > 0L) {
sessionTimeout = Math.max(TimeUnit.SECONDS.toMinutes(sessionTimeout), 1L);
}
return sessionTimeout;
}
- 在
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory#configureSession
方法中,有获取Session过期时间的调用,调用的就是以上代码,它会判断配置的时间和1分钟做比较,如果比一分钟小,过期时间就取一分钟,也就是说最短过期时间就是一分钟,我们上面配置的10秒钟是不生效的。
配置Session过期后页面跳转
- 还是在我们
主配置类BrowserSecurityConfig
中加上页面跳转配置路径
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(meicloudAuthenticationSuccessHandler)
.failureHandler(meicloudAuthenticationFailureHandler)
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(3600)
.userDetailsService(userDetailsService)
.and()
// 配置Session过期之后的路径跳转
.sessionManagement()
.invalidSessionUrl("/session/invalid")
.and()
.authorizeRequests()
.antMatchers("/authentication/require", securityProperties.getBrowser().getSignInPage(), "/code/*", "/session/invalid").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.apply(smsCodeAuthenticationSecurityConfig);
}
Session并发控制
问题描述
- 所谓并发控制就是用户在这个手机登录后,他又在另一个手机登录相同账户,对于之前登录的账户
是否需要被挤兑,或者说在第二次登录时限制它登录
,更或者像腾讯视频VIP账号一样,最多只能五个人同时登录,第六个人限制登录,看看这些效果Spring Security是如何实现的?
挤兑登录(后面登录把前面登录的用户Session失效掉)
- 也是在主配置类中配置一个
maxmumSessions属性
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(meicloudAuthenticationSuccessHandler)
.failureHandler(meicloudAuthenticationFailureHandler)
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(3600)
.userDetailsService(userDetailsService)
.and()
// Session相关配置
.sessionManagement()
// 配置Session过期之后的路径跳转
.invalidSessionUrl("/session/invalid")
// 配置最大Session数量,配置为1表示后面登录的会把前面登录的用户挤兑
.maxmumSessions(1)
// 配置挤兑后处理类
.expireSessionStrategy(new MeicloudExpiredSessionStrategy())
.and()
.authorizeRequests()
.antMatchers("/authentication/require", securityProperties.getBrowser().getSignInPage(), "/code/*", "/session/invalid").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.apply(smsCodeAuthenticationSecurityConfig);
}
- 配置上一个用户被挤兑之后的处理类,
实现SessionInformationExpiredStrategy接口
,然后还需要在主配置类添加expireSessionStrategy
将处理类配置上去。
public class MeicloudExpiredSessionStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
event.getResponse().getWriter().write("您已被挤兑下线!");
}
}
限制登录(登录达到一定数量后,限制后续登录)
- 就是说前面用户登录达到配置的数值之后,后面的用户无法继续登录,需要在主配置类配置
maxSessionPreventsLogin属性
。
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(meicloudAuthenticationSuccessHandler)
.failureHandler(meicloudAuthenticationFailureHandler)
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(3600)
.userDetailsService(userDetailsService)
.and()
// Session相关配置
.sessionManagement()
// 配置Session过期之后的路径跳转
.invalidSessionUrl("/session/invalid")
// 配置最大Session数量,配置为1表示后面登录的会把前面登录的用户挤兑
.maxmumSessions(1)
// 将该属性配置成true表示限制最大登录用户数量,上面配置的数值是1,表示只能登录一次用户
.maxSessionPreventsLogin(true)
// 配置挤兑后处理类
.expireSessionStrategy(new MeicloudExpiredSessionStrategy())
.and()
.authorizeRequests()
.antMatchers("/authentication/require", securityProperties.getBrowser().getSignInPage(), "/code/*", "/session/invalid").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.apply(smsCodeAuthenticationSecurityConfig);
}
集群Session管理
问题描述
- 默认情况下,Session是放在中间服务器里面的,比如说tomcat,当应用集群部署同时没有做其他同步Session处理的话,就会出现用户在A机器登录,这时会把登录信息放在A机器Session上,而后续很多请求是请求B机器,B机器并没有登录信息,于是
B机器仍然会要求用户再次登录,这显然不合理
,所以这种情况来看看可以如何处理?
处理方案
Session是基于服务器内存,不支持集群
,而Session目的是存放登录信息,要支持集群的话,可以把登录信息保存到分布式集群的存储结构上,比如Redis
中。- 原始Session和分布式Session流程图
实际操作
- 引入jar包
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
- Spring支持的Session存储类型,在
StoreType枚举类里面
,这里建议放在Redis中,一个原因是获取登录信息非常频繁,正好Redis获取速度快,另一个Session有过期时间,而Redis天生就支持设置Key的过期限制。
public enum StoreType {
REDIS,
MONGO,
JDBC,
HAZELCAST,
HASH_MAP,
NONE;
private StoreType() {
}
}
- 配置存储类型,在application.properties文件配置,不需要其他代码,就已经支持将登录信息存放到Redis了,可以启多个端口进行一个测试。
spring.session.store-type = REDIS