文章目錄
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