session管理

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
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章