文章目录
session超时处理
demo的application中加入超时配置
# 默认30分钟 server.servlet.session.timeout=30m
server.servlet.session.timeout= 10s
但是当我们登录上去发下session的超时时间远超过了10s
原因
看 TomcatEmbeddedServletContainerFactory 类
但找不到这个类
Spring Boot 2中缺少TomcatEmbeddedServletContainerFactory(TomcatEmbeddedServletContainerFactory is missing in Spring Boot 2)
为了支持反应用例,嵌入式容器
包结构已经被非常广泛地重构。
EmbeddedServletContainer已重命名为WebServer,
org.springframework.boot.context.embedded包已重新定位
到org.springframework.boot.web.server。相应地,
EmbeddedServletContainerCustomizer已重命名为
WebServerFactoryCustomizer。
例如,如果您使用TomcatEmbeddedServletContainerFactory自定义嵌入式Tomcat容器
回调接口,
你现在应该使用TomcatServletWebServerFactory,如果你使用
一个EmbeddedServletContainerCustomizer bean,你现在应该使用
WebServerFactoryCustomizer bean。
看TomcatServletWebServerFactory类
private void configureSession(Context context) {
long sessionTimeout = this.getSessionTimeoutInMinutes();//以分钟为单位读取超时时间
context.setSessionTimeout((int)sessionTimeout);//超时时间取整,则说明超时时间最低为1分钟
Boolean httpOnly = this.getSession().getCookie().getHttpOnly();
if (httpOnly != null) {
context.setUseHttpOnly(httpOnly);
}
if (this.getSession().isPersistent()) {
Manager manager = context.getManager();
if (manager == null) {
manager = new StandardManager();
context.setManager((Manager)manager);
}
this.configurePersistSession((Manager)manager);
} else {
context.addLifecycleListener(new TomcatServletWebServerFactory.DisablePersistSessionListener());
}
}
超时页面提醒
BrowserSecurityConfig
注意 路径前必须有杠
BrowserSecurityController
@RequestMapping("/session/invalid")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)//401未授权状态码
public SimpleResponse sessionInvalid(HttpServletRequest request, HttpServletResponse response){
String message = "session失效";
return new SimpleResponse(message);
}
测试
当跳转到这个页面时,可以由前台决定是给一个美化页面还是重定向到登录
session并发控制
一个用户只能有一个session
当后面再登录时,后面的session替换掉前面的session
BrowserSecurityConfig
并发策略
public class MyExpiredSessionStrategy implements SessionInformationExpiredStrategy {
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
event.getResponse().setContentType("application/json;charset=UTF-8");
event.getResponse().getWriter().write("并发登录!");
}
}
分别在两个浏览器登录,
然后再回到第一个登录的浏览器访问
第二种情况 /当session数量达到最大时 阻止后面的登录
此时 在另一个浏览器重新登录,如下
重构
见代码
集群session管理
当一款应用让用户使用时都会部署一个集群
这个集群至少部署两台机器
前面再做一个负载均衡,当一台机器挂掉后,应用则还可以正常使用,为用户服务
这种情况下 我们的session如何处理
即,服务器session独立管理
而不是每个机器管理自己的session
spring专门提供了一个依赖来处理这种情况
在browser模块中我们引入了一个依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.5.RELEASE</version>
<!--这个要写版本号,也不知道为什么Spring IO Platform么有对他进行版本控制-->
</dependency>
我们只需要告诉它session的存储是什么,即存储的地址和端口
它支持的存储如下
/**
* Supported Spring Session data store types.
*
* @author Tommy Ludwig
* @author Eddú Meléndez
* @author Vedran Pavic
* @since 1.4.0
*/
public enum StoreType {
/**
* Redis backed sessions.
*/
REDIS,
/**
* MongoDB backed sessions.
*/
MONGODB,
/**
* JDBC backed sessions.
*/
JDBC,
/**
* Hazelcast backed sessions.
*/
HAZELCAST,
/**
* No session data-store.
*/
NONE
}
一般使用redis来做这个存储
1 是session需频繁使用,redis性能高
2 是redis本事就有过期时间与session的过期时间可以对应
下载redis
https://redis.io/download
稳定版
下载以后解压安装并启动
[root@rain redis-5.0.5]# mv //tmp/VMwareDnD/vzwLhB/redis-5.0.5.tar.gz ./
[root@rain redis-5.0.5]# tar -xvf redis-5.0.5.tar.gz
[root@rain redis-5.0.5]# cd redis-5.0.5/
[root@rain redis-5.0.5]# make
·············
Hint: It’s a good idea to run ‘make test’ ?
make[1]: 离开目录“/opt/work/tools/redis-5.0.5/src”
[root@rain redis-5.0.5]# ./src/redis-server
他会在6379这个端口提供redis服务
demo的application配置session存储类型和redis连接
spring.session.store-type=redis
#spring.session.store-type=none
## Redis服务器地址
spring.redis.host=192.168.85.134
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=123456
redis我是在虚拟机里配的,宿主连接
启动
报错
ERROR 20224 --- [ost-startStop-1] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'sessionRepositoryFilterRegistration' defined in class path resource [org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.class]: Unsatisfied dependency expressed through method 'sessionRepositoryFilterRegistration' parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.session.RedisSessionConfiguration$SpringBootRedisHttpSessionConfiguration': Injection of autowired dependencies failed; nested exception is java.lang.NoSuchMethodError: org.springframework.boot.autoconfigure.session.RedisSessionConfiguration$SpringBootRedisHttpSessionConfiguration.setCleanupCron(Ljava/lang/String;)V
原因
依赖错了
我用的是spring-session
实际应该用spring-session-data-redis
依赖替换后启动成功
访问,但是图片验证码没出来
报错
org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.whale.security.core.validate.image.ImageCode]
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:96) ~[spring-data-redis-2.0.12.RELEASE.jar:2.0.12.RELEASE]
意思是ImageCode序列化出现了问题
因为我们的session是用redis管理了,而我们是把ImageCode放在了session里面
放在redis里的东西都需要可序列化
一个类实现序列化的时候
类中的属性都需要实现序列化
但是在ImageCode中有一个BufferedImage属性,而BufferedImage是jdk提供的,它没有实现序列化接口
所有我们session中不放图片,而是直接放验证码
AbstractValidateCodeProcessor
/**
* 保存校验码
*
* @param request
* @param validateCode
*/
private void save(ServletWebRequest request, C validateCode) {
// sessionStrategy.setAttribute(request, getSessionKey(request).toUpperCase(), validateCode);
ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime());
// sessionStrategy.setAttribute(request, getSessionKey(request), validateCode);
sessionStrategy.setAttribute(request, getSessionKey(request), code);
}
重新启动
验证码可以正常显示
再再8081端口启动一个实例
测试
同一个浏览器中
8080端口登录后
8081端口共享这个session
退出处理
退出需要访问一个特定的路径
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
index
<a href="/logout"> 退出</a>
</body>
</html>
退出步骤
使当前session失效
清除与当前用户相关的remember-me记录
清空当前的securityContext
重定向到登录页
更改默认退出配置
自定义退出路径
index
<a href="/logout"> 退出</a>
<a href="/signOut"> 我的退出</a>
BrowserSecurityConfig
测试 ok
退出成功处理
<body>
<h2>退出成功</h2>
</body>
BrowserSecurityConfig
test
自定义退出成功处理器
LogoutSuccessHandler
public class WhaleLogoutSuccessHandler implements LogoutSuccessHandler {
// private SecurityProperties securityProperties;
private String signOutUrl;
private ObjectMapper objectMapper = new ObjectMapper();
public WhaleLogoutSuccessHandler(String signOutUrl){
this.signOutUrl = signOutUrl;
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("退出成功");
// String signOutUrl = securityProperties.getBrowser().getSignOutUrl();
//如果没有配置退出页面
if(StringUtils.isBlank(signOutUrl)){
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString("LogoutSuccessHandler退出成功"));
}else {
response.sendRedirect(signOutUrl);
}
}
BrowserSecurityBeanConfig
@Bean
@ConditionalOnMissingBean(LogoutSuccessHandler.class)
public LogoutSuccessHandler logoutSuccessHandler(){
return new WhaleLogoutSuccessHandler(securityProperties.getBrowser().getSignOutUrl());
}
BrowserSecurityConfig