Spring Security -- session管理(转载)
用户登录成功后,信息保存在服务器的session中,并返回给用户一个sessionid,sessionid是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,当用户再次请求时,将携带该sessionId,如果在服务器中能够找到该sessionid,则表示用户登录成功。这节将会学习如何管理session。这节将会在Spring Security -- 添加图形验证码(转载)的基础上继续扩展。
一、session超时设置
1、配置session
session超时时间也就是用户登录的有效时间。要设置session超时时间很简单,只需要在配置文件application.yml中添加:
#session设置超时
server:
servlet:
session:
timeout: 3600
单位为秒,通过上面的配置,session的有效期为一个小时。值得注意的是,session的最小有效期为60秒,也就是说即使你设置为小于60秒的值,其有效期还是为60秒。
session失效后,刷新页面后将跳转到认证页面,我们可以再添加一些配置,自定义session失效后的一些行为。在Spring Security中配置session管理器,并配置session失效后要跳转的URL:
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加图片验证码校验过滤器 .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加手机短信验证码校验过滤器 .authorizeRequests() // 授权配置 .antMatchers("/code/image","/code/sms","/session/invalid") .permitAll() // 无需认证的请求路径 .anyRequest() // 任何请求 .authenticated() //都需要身份认证 .and() .formLogin() // 或者httpBasic() .loginPage("/login") // 指定登录页的路径 //我们的form表单action是将请求提交到/login/mobile页面,而在Spring Security中配置的 .loginProcessingUrl("/login") 值为/login,这两者为什么不一样呢?这样做的目的是通过指定Spring Security中的UsernamePasswordAuthenticationFilter的拦截目标为post请求/login,从而使得该过滤器不会拦截/login/mobile请求;那么针对/login/mobile请求我们会仿照UsernamePasswordAuthenticationFilter定义自己的过滤器,然后对其进行认证; .loginProcessingUrl("/login") // 指定自定义form表单提交请求的路径 .successHandler(authenticationSucessHandler) // 处理登录成功 .failureHandler(authenticationFailureHandler) // 处理登录失败 // 必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环) // 这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。 .permitAll() // .and() // .rememberMe() // .tokenRepository(persistentTokenRepository) // 配置 token 持久化仓库 // .tokenValiditySeconds(3600) // remember 过期时间,单为秒 // .userDetailsService(userDetailsService) // 处理自动登录逻辑 // .and() // .logout() // .permitAll() .and() .sessionManagement() //添加session管理器 .invalidSessionUrl("/session/invalid") //Session失效后跳转到这个链接 .and() //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉 .csrf().disable() .apply(smsAuthenticationConfig); // 将短信验证码认证配置加到 Spring Security 中 添加一个安全配置其到http的configurers集合 }
2、SessionController
上面配置了session失效后跳转到/session/invalid,并且将这个URL添加到了免认证路径中。
在包com.zy.example.conmtroller下创建类SessionController,添加一个方法,映射该请求:
package com.zy.example.controller; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseStatus; /** * @Author: zy * @Description: session路由 * @Date: 2020/2/16 */ public class SessionController { @GetMapping("/session/invalid") @ResponseStatus(HttpStatus.UNAUTHORIZED) public String sessionInvalid(){ return "session已失效,请重新认证"; } }
3、测试
为了演示,我们将session的超时时间设置为最小值60秒,重启项目,访问http://127.0.0.1:8080/index,并成功认证后输出:
等待60秒并刷新页面:
可看到请求跳转到了我们自定义的/session/invalidURL上。
4、session并发控制
session并发控制可以控制一个账号同一时刻最多能登录多少个。我们在Spring Security配置中继续添加session相关配置:
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加图片验证码校验过滤器 .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加手机短信验证码校验过滤器 .authorizeRequests() // 授权配置 .antMatchers("/code/image","/code/sms","/session/invalid") .permitAll() // 无需认证的请求路径 .anyRequest() // 任何请求 .authenticated() //都需要身份认证 .and() .formLogin() // 或者httpBasic() .loginPage("/login") // 指定登录页的路径 //我们的form表单action是将请求提交到/login/mobile页面,而在Spring Security中配置的 .loginProcessingUrl("/login") 值为/login,这两者为什么不一样呢?这样做的目的是通过指定Spring Security中的UsernamePasswordAuthenticationFilter的拦截目标为post请求/login,从而使得该过滤器不会拦截/login/mobile请求;那么针对/login/mobile请求我们会仿照UsernamePasswordAuthenticationFilter定义自己的过滤器,然后对其进行认证; .loginProcessingUrl("/login") // 指定自定义form表单提交请求的路径 .successHandler(authenticationSucessHandler) // 处理登录成功 .failureHandler(authenticationFailureHandler) // 处理登录失败 // 必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环) // 这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。 .permitAll() // .and() // .rememberMe() // .tokenRepository(persistentTokenRepository) // 配置 token 持久化仓库 // .tokenValiditySeconds(3600) // remember 过期时间,单为秒 // .userDetailsService(userDetailsService) // 处理自动登录逻辑 // .and() // .logout() // .permitAll() .and() //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉 .csrf().disable() .apply(smsAuthenticationConfig) // 将短信验证码认证配置加到 Spring Security 中 添加一个安全配置其到http的configurers集合 .and() .sessionManagement() //添加session管理器 .invalidSessionUrl("/session/invalid") //Session失效后跳转到这个链接 .maximumSessions(1) .expiredSessionStrategy(sessionInformationExpiredStrategy); }
maximumSessions配置了最大session并发数量为1个,如果admin这个账户登录后,在另一个客户端也使用admin账户登录,那么第一个使用admin登录的账户将会失效,类似于一个先入先出队列。sessionInformationExpiredStrategy配置了session在并发下失效后的处理策略,这里为我们在com.zy.example.config包下自定义策略CustomSessionInformationExpiredStrategy :
package com.zy.example.config; import org.springframework.http.HttpStatus; import org.springframework.security.web.session.SessionInformationExpiredEvent; import org.springframework.security.web.session.SessionInformationExpiredStrategy; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Author: zy * @Description: 配置了Session在并发下失效后的处理策略 * @Date: 2020/2/16 */ @Component public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy { @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { HttpServletResponse response = event.getResponse(); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("application/json;charset=utf-8"); response.getWriter().write("您的账号已经在别的地方登录,当前登录已失效。如果密码遭到泄露,请立即修改密码!"); } }
为了演示这个效果,我们先将session超时时间设置久一点,比如3600秒,然后重启项目,在Chrome里使用admin账户登录。
登录成功后,在IE上也是用admin账户登录,登录成功后回到Chrome,刷新页面,效果如下所示:
除了后者将前者踢出的策略,我们也可以控制当session达到最大有效数的时候,不再允许相同的账户登录。
要实现这个功能只需要在上面的配置中添加:
...... .and() .sessionManagement() // 添加 Session管理器 .invalidSessionUrl("/session/invalid") // Session失效后跳转到这个链接 .maximumSessions(1) .maxSessionsPreventsLogin(true) .expiredSessionStrategy(sessionInformationExpiredStrategy) .and() ......
重启系统,在Chrome上登录admin账户后,在IE上尝试使用admin账户登录:
可以看到登录受限。在实际开发中,发现session并发控制只对Spring Security默认的登录方式——账号密码登录有效,而像短信验证码登录,社交账号登录并不生效,解决方案可以开源项目https://github.com/wuyouzhuguli/FEBS-Security。
5、session集群处理
session集群听着高大上,其实实现起来很简单。当我们登录成功后,用户认证的信息存储在session中,而这些session默认是存储在运行运用的服务器上的,比如Tomcat,netty等。当应用集群部署的时候,用户在A应用上登录认证了,后续通过负载均衡可能会把请求发送到B应用,而B应用服务器上并没有与该请求匹配的认证session信息,所以用户就需要重新进行认证。要解决这个问题,我们可以把session信息存储在第三方容器里(如Redis集群),而不是各自的服务器,这样应用集群就可以通过第三方容器来共享session了。
我们引入Redis和Spring Session依赖:
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
然后在yml中配置Session存储方式为Redis:
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了