单机session管理
到目前为止三个功能:
- 用户名 + 密码登录
- 手机号 + 短信登录
- 社交网站登录
前两种使用表单提交方式完成,后一种使用oath授权完成;
虽然表现方式和处理流程不同,但是有一个共同点,认证后的用户信息是存放在session中的;
-
session超时
- 如何管理超时时间
- 超时后如何处理
-
session并发 : a 机器登录,又在b机器登录的场景下,只运行一台机器登录
- 如何保持后来者生效,之前的失效
-
集群session管理
均衡负载如果没有做session粘连的话,会出现登录在a机器,请求数据在b机器
session超时
注意:server.session.timeout 已经过时了
server:
port: 80
servlet:
session:
timeout: 10s
重启测试,发现并没有超时;在以下源码中限制了最小为1分钟;
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#configureSession
实现session超时提醒
session超时之后,再次访问进行一个提醒要怎么做呢?
cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityConfig#configure
配置下session失效跳转的url地址,这个地址需要我们实现。你可以做任何的业务逻辑;同时记得放行该地址,否则又被拦截授权了
.and()
.sessionManagement()
.invalidSessionUrl("/session/invalid")
cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityController#sessionInvalid
这里就打印下消息
@GetMapping("/session/invalid")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public SimpleResponse sessionInvalid() {
String message = "session失效";
return new SimpleResponse(message);
}
实现session并发登录控制
// 配置地址或则策略类
.maximumSessions(1) //限制同一个用户只能有一个session登录
//.expiredUrl("/session/expired") // 也可以跳转到一个服务
.expiredSessionStrategy(new MySessionInformationExpiredStrategy()) // 失效后的策略。定制型更高,失效前的请求还能拿到
编写策略类
/**
* session并发登录失效策略
* @author : zhuqiang
* @version : V1.0
* @date : 2018/8/6 21:28
*/
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
// 该对象能获取到访问失效前的url地址
event.getResponse().setContentType("application/json;charset=UTF-8");
event.getResponse().getWriter().write("session并发登录");
}
}
测试:在不同浏览器登录,然后在最开始登录的浏览器中访问一个服务查看下;
实现session并发登录策略2
.maximumSessions(1) //限制同一个用户只能有一个session登录
.maxSessionsPreventsLogin(true) // 当session达到最大后,阻止后登录的行为
测试会提示:“Maximum sessions of 1 for this principal exceeded”
代码重构,消除重复代码,可提供可配置功能
这里尝试自己看一遍视频,然后全程去思考如何提出来。不行的时候再看源码
重构的时候需要注意一个坑:
/** 当session达到最大值后,是阻止用户登录还是剔除掉已登录用户
* fasle : 会走{@link cn.mrcode.imooc.springsecurity.securitybrowser.session.MySessionInformationExpiredStrategy}
* true:会阻止登录,这个阻止登录的个性化消息没有设置,看源码的时候好像可以覆盖那个过滤器;设置为true会看到报错信息,然后就可以查看覆盖说明了
* */
private boolean maxSessionsPreventsLogin;
集群session管理
spring Security 是基于session的安全框架。所以就会有这个问题
// 该项目用来做浏览器端的所以需要有session
// 提供集群环境下的session管理,也没有被管理到,需要自己添加
//如果在启动的时候报错,可以通过配置 yml文件中 spring: session: store-type: none
compile('org.springframework.session:spring-session:1.3.3.RELEASE')
org.springframework.boot.autoconfigure.session.StoreType 标识支持的类型,
上面设置成none其实就是使用原生j2ee单机服务器session这种模式
为上面使用redis?
- 所有请求都要使用session
- 有过期时间,Redis天生支持
依赖:特别注意:spring-session:1.3.3.RELEASE在高版本的spring boot autoconfig中已经不支持了;
需要分开引用下面的包
// 该项目用来做浏览器端的所以需要有session
// 提供集群环境下的session管理,也没有被管理到,需要自己添加
//如果在启动的时候报错,可以通过配置 yml文件中 spring: session: store-type: none
// compile('org.springframework.session:spring-session:1.3.3.RELEASE')
compile('org.springframework.session:spring-session-core')
compile('org.springframework.session:spring-session-data-redis')
开始改造
依赖添加之后,更改配置文件
spring:
session:
store-type: redis
访问登录页面:/imocc-signIn.html; 发现验证码图片无法显示,报错了。没有序列化
// 相关报错序列化问题的地方都记得修改
public class ImageCode extends ValidateCode implements Serializable{
private static final long serialVersionUID = -703011095085705839L;
private BufferedImage image; // 看这里,是一个复杂的图片对象
怎么处理这个图片对象呢?考虑在存和取的地方下功夫
cn.mrcode.imooc.springsecurity.securitycore.validate.code.impl.AbstractValidateCodeProcessor#save
private void save(ServletWebRequest request, C validateCode) {
// 不保存图片对象到redis session中,无法序列化
// 因为在验证的时候不需要图片对象
ValidateCode code = new ValidateCode(validateCode.getCode(), validateCode.getExpireTime());
sessionStrategy.setAttribute(request, getSessionKey(), code);
}
测试
- 启动两个不同端口的项目
- 在同一个浏览器中
- 其中一个端口先登录 :localhost:80
- 然后新开一个页面直接打开另外一个端口访问 localhost:80/user/me
之前配置的所有功能正常,只是在session失效只允许一个登录这个功能,可能会有一点点的延迟
退出登录
- 如何退出登录
- Spring security 默认的退出处理逻辑
- 与退出登录相关的配置
默认退出处理逻辑
- 使当前session失效
- 清除与当前用户相关的remember-me记录
- 清空当前的SecurityContext
- 重定向到登录页
还记得以前登录的时候有一个默认的登录地址:/login,同样默认了一个退出/logout;
直接访问该地址:如果看到下面的报错,请检查 之前开发记住我的功能的配置
Mon Aug 06 23:55:25 CST 2018
There was an unexpected error (type=Internal Server Error, status=500).
PreparedStatementCallback; bad SQL grammar [delete from persistent_logins where username = ?]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'imooc-demo.persistent_logins' doesn't exist
cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityConfig#persistentTokenRepository
记住我功能的配置,自动生成表结构;像我公司和家来回学习的人。有时候会忘记
解决之后,访问 /logout 发现跳转到了认证页面
http://localhost:8080/authentication/require?logout
上面的步骤,退出之后会重定向到登录页,我们的登录页是自定义的,处理授权前的连接,所以就跳转到这里了
cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityConstants#DEFAULT_UNAUTHENTICATION_URL
退出常用基本配置
.logout()
.logoutUrl("/singout") // 退出请求路径
.logoutSuccessUrl("/imocc-signOut.html") // 退出成功跳转到的地址
.logoutSuccessHandler() // 与logoutSuccessUrl互斥,有handler则logoutSuccessUrl失效
.deleteCookies("JSESSIONID")
退出实现
.logout()
// .logoutUrl("/singout") // 退出请求路径
// 与logoutSuccessUrl互斥,有handler则logoutSuccessUrl失效
// 通过处理器增加配置了页面则跳转到页面,没有则输出json
.logoutSuccessHandler(logoutSuccessHandler)
.deleteCookies("JSESSIONID")
使用handler来处理退出逻辑
package cn.mrcode.imooc.springsecurity.securitybrowser.logout;
import cn.mrcode.imooc.springsecurity.securitybrowser.support.SimpleResponse;
import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author : zhuqiang
* @version : V1.0
* @date : 2018/8/7 0:18
*/
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
private org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
private ObjectMapper objectMapper = new ObjectMapper();
private SecurityProperties securityProperties;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 当退出成功的时候,如果配置了一个页面,则跳转到页面,
// 没有配置页面则打印session
// 这里增加了一个属性,默认为空
String signOutUrl = securityProperties.getBrowser().getSignOutUrl();
if (StringUtils.isBlank(signOutUrl)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse("退出成功")));
} else {
response.sendRedirect(signOutUrl);
}
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
配置处理器的初始化bean
cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityBeanConfig#logoutSuccessHandler
@Bean
@ConditionalOnMissingBean(LogoutSuccessHandler.class)
public LogoutSuccessHandler logoutSuccessHandler() {
MyLogoutSuccessHandler myLogoutSuccessHandler = new MyLogoutSuccessHandler();
myLogoutSuccessHandler.setSecurityProperties(securityProperties);
return myLogoutSuccessHandler;
}
本文由 liyunfei 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jul 25,2022