1. 程式人生 > >Spring Session Redis 在不同服務間共享 Session 時的類共享方案

Spring Session Redis 在不同服務間共享 Session 時的類共享方案

Spring Session Redis 是不安全的

當在多服務之間使用 Spring Session Redis 進行 Session 共享要非常小心,因為它很不安全,很有可能導致整個服務例項不可用,無法處理任何請求。其中比較危險的地方就是在進行序列化,反序列化的時候(這種型別的錯誤尤其容易在沒有開發規範的團隊內發生,就是什麼樣的資料可以往共享儲存裡面存,什麼樣的不能存。存的時候要以什麼樣的格式去存,這些都要有規定才比較安全。因為共享儲存是會影響到別人的不僅僅是為了自己的服務用起來方便)。RedisSerializer 介面的實現都是在序列化和反序列化出錯的時候直接丟擲異常從而導致整個請求錯誤。

public interface RedisSerializer<T> {

	/**
	 * Serialize the given object to binary data.
	 * 
	 * @param t object to serialize
	 * @return the equivalent binary data
	 */
	byte[] serialize(T t) throws SerializationException;

	/**
	 * Deserialize an object from the given binary data.
	 * 
	 * @param bytes object binary representation
	 * @return the equivalent object instance
	 */
	T deserialize(byte[] bytes) throws SerializationException;
}

 

下面用一張圖來說明我遇到的問題。Spring Session 的誕生老實說並不是為了分散式系統,而是為集群系統提供了一種 Session 解決方案。但是我們把 Spring Session 用在了分散式系統上用以解決 Session 共享的問題老實說本身就是有點難為人家 Spring Session 了 。

 

Spring Session 實現 Session 共享的大致原理

Spring Session 實現 Session 共享的大致原理如下圖所示 , 使用一個 Filter 來攔截所有請求,在攔截到請求之後對 HttpServletRequest 和 HttpServletResponse 進行包裝 (HttpServletRequestWrapper  , HttpServletResponseWrapper)。在包裝中對 session 進行控制 ,將 session 資料都儲存在第三方儲存當中。

 

Spring Session Redis 在不同服務間共享 Session 時的類共享方案

在瞭解了 Spring Session 的工作原理後再去考慮這個問題就有頭緒了 。還是通過圖形的方式來做大概的說明。

 

學習 Spring Session 的 SessionRepositoryFilter 的實現方式 , 新增一個 Filter 順序在 SessionRepositoryFilter 之後 , 在攔截過程中包裝 HttpServletRequest , 重寫 getSession(boolean create)  和 getSession() 方法, 自定義一個 SafetyHttpSessionWrapper 包裝 Session ,重寫 setAttribute(String name , Object value) 函式 , 在儲存屬性成功後利用 redis 的釋出訂閱機制傳送訊息到 redis , 訊息的內容為所儲存物件的 .class 檔案資料。在訊息訂閱端 , 接收到訊息後利用 javassist 和 net.bytebuddy.dynamic.loading.ByteArrayClassLoader 將 .class 檔案資料載入轉換成 Class 的例項物件,但是這個  Class 例項的範圍被限定在 ByteArrayClassLoader 中 , 而這個 ByteArrayClassLoader 是提供給 RedisSerializer 內部使用的 , 比如 JdkSerializationRedisSerializer , GenericJackson2JsonRedisSerializer 都需要使用到 ClassLoader 。這樣當 服務A 在儲存任何自定義的物件在 Session 中時, 訪問服務 B 也不會出現讀取 Session 反序列化 ClassNotFoundException 的錯誤了。

 

初版程式碼實現

以上思路的程式碼實現在 WORKX 專案中,感興趣的同學可以參考 org.hepeng.workx.spring.session.redis.serializer 這個包下的程式碼。從 org.hepeng.workx.spring.session.redis.serializer.SafetyRedisSerializerConfiguration 這個類入手看原始碼。目前 RedisSerializer 元件只支援了 JdkSerializationRedisSerializer , GenericJackson2JsonRedisSerializer 以