API 文件

使用 Session

Session 是一個簡化的鍵值對 Map

典型用法可能如下所示

class RepositoryDemo<S extends Session> {

	private SessionRepository<S> repository; (1)

	void demo() {
		S toSave = this.repository.createSession(); (2)

		(3)
		User rwinch = new User("rwinch");
		toSave.setAttribute(ATTR_USER, rwinch);

		this.repository.save(toSave); (4)

		S session = this.repository.findById(toSave.getId()); (5)

		(6)
		User user = session.getAttribute(ATTR_USER);
		assertThat(user).isEqualTo(rwinch);
	}

	// ... setter methods ...

}
1 我們建立一個帶有泛型型別 SSessionRepository 例項,S 擴充套件了 Session。泛型型別在我們的類中定義。
2 我們使用 SessionRepository 建立一個新的 Session 並將其賦值給 S 型別的變數。
3 我們與 Session 互動。在我們的示例中,我們演示了將 User 儲存到 Session
4 我們現在儲存 Session。這就是為什麼我們需要泛型型別 SSessionRepository 只允許儲存使用相同 SessionRepository 建立或檢索的 Session 例項。這允許 SessionRepository 進行特定於實現的最佳化(即只寫入已更改的屬性)。
5 我們從 SessionRepository 中檢索 Session
6 我們從 Session 中獲取持久化的 User,而無需顯式轉換我們的屬性。

Session API 還提供了與 Session 例項過期相關的屬性。

典型用法可能如下所示

class ExpiringRepositoryDemo<S extends Session> {

	private SessionRepository<S> repository; (1)

	void demo() {
		S toSave = this.repository.createSession(); (2)
		// ...
		toSave.setMaxInactiveInterval(Duration.ofSeconds(30)); (3)

		this.repository.save(toSave); (4)

		S session = this.repository.findById(toSave.getId()); (5)
		// ...
	}

	// ... setter methods ...

}
1 我們建立一個帶有泛型型別 SSessionRepository 例項,S 擴充套件了 Session。泛型型別在我們的類中定義。
2 我們使用 SessionRepository 建立一個新的 Session 並將其賦值給 S 型別的變數。
3 我們與 Session 互動。在我們的示例中,我們演示了更新 Session 在過期前可以不活動的持續時間。
4 我們現在儲存 Session。這就是為什麼我們需要泛型型別 SSessionRepository 只允許儲存使用相同 SessionRepository 建立或檢索的 Session 例項。這允許 SessionRepository 進行特定於實現的最佳化(即只寫入已更改的屬性)。當 Session 儲存時,上次訪問時間會自動更新。
5 我們從 SessionRepository 中檢索 Session。如果 Session 已過期,結果將為 null。

使用 SessionRepository

SessionRepository 負責建立、檢索和持久化 Session 例項。

如果可能,您不應直接與 SessionRepositorySession 互動。相反,開發人員應優先透過 HttpSessionWebSocket 整合間接與 SessionRepositorySession 互動。

使用 FindByIndexNameSessionRepository

Spring Session 最基本的 Session 使用 API 是 SessionRepository。此 API 故意設計得非常簡單,以便您可以輕鬆地提供具有基本功能的附加實現。

一些 SessionRepository 實現也可能選擇實現 FindByIndexNameSessionRepository。例如,Spring 的 Redis、JDBC 和 Hazelcast 支援庫都實現了 FindByIndexNameSessionRepository

FindByIndexNameSessionRepository 提供了一種方法來查詢所有具有給定索引名稱和索引值的會話。作為所有提供的 FindByIndexNameSessionRepository 實現支援的常見用例,您可以使用便捷方法查詢特定使用者的所有會話。這是透過確保名為 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 的會話屬性填充了使用者名稱來完成的。由於 Spring Session 不知道正在使用的身份驗證機制,因此您有責任確保該屬性被填充。以下清單中顯示瞭如何使用此方法的示例。

String username = "username";
this.session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
某些 FindByIndexNameSessionRepository 的實現提供了自動索引其他會話屬性的鉤子。例如,許多實現會自動確保當前的 Spring Security 使用者名稱使用索引名稱 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 進行索引。

會話索引後,您可以使用類似於以下程式碼的方式進行查詢。

String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);

使用 ReactiveSessionRepository

ReactiveSessionRepository 負責以非阻塞和反應式方式建立、檢索和持久化 Session 例項。

如果可能,您不應直接與 ReactiveSessionRepositorySession 互動。相反,您應優先透過 WebSession 整合間接與 ReactiveSessionRepositorySession 互動。

使用 @EnableSpringHttpSession

您可以將 @EnableSpringHttpSession 註解新增到 @Configuration 類中,以將 SessionRepositoryFilter 作為名為 springSessionRepositoryFilter 的 bean 公開。為了使用該註解,您必須提供單個 SessionRepository bean。以下示例顯示瞭如何操作。

@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {

	@Bean
	public MapSessionRepository sessionRepository() {
		return new MapSessionRepository(new ConcurrentHashMap<>());
	}

}

請注意,沒有為您配置會話過期基礎設施。這是因為會話過期等功能高度依賴於實現。這意味著,如果您需要清理過期會話,則您有責任清理過期會話。

使用 @EnableSpringWebSession

您可以將 @EnableSpringWebSession 註解新增到 @Configuration 類中,以將 WebSessionManager 作為名為 webSessionManager 的 bean 公開。要使用該註解,您必須提供單個 ReactiveSessionRepository bean。以下示例顯示瞭如何操作。

@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
public class SpringWebSessionConfig {

	@Bean
	public ReactiveSessionRepository reactiveSessionRepository() {
		return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
	}

}

請注意,沒有為您配置會話過期基礎設施。這是因為會話過期等功能高度依賴於實現。這意味著,如果您需要清理過期會話,則您有責任清理過期會話。

使用 RedisSessionRepository

RedisSessionRepository 是一個使用 Spring Data 的 RedisOperations 實現的 SessionRepository。在 Web 環境中,這通常與 SessionRepositoryFilter 結合使用。請注意,此實現不支援釋出會話事件。

例項化 RedisSessionRepository

您可以在以下清單中看到如何建立新例項的典型示例。

RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository<? extends Session> repository = new RedisSessionRepository(redisTemplate);

有關如何建立 RedisConnectionFactory 的更多資訊,請參閱 Spring Data Redis 參考。

使用 @EnableRedisHttpSession

在 Web 環境中,建立新的 RedisSessionRepository 的最簡單方法是使用 @EnableRedisHttpSession。您可以在 示例和指南(從這裡開始) 中找到完整的示例用法。您可以使用以下屬性自定義配置。

enableIndexingAndEvents * enableIndexingAndEvents:是否使用 RedisIndexedSessionRepository 而不是 RedisSessionRepository。預設值為 false。 * maxInactiveIntervalInSeconds:會話過期前的時間量,以秒為單位。 * redisNamespace:允許為會話配置應用程式特定的名稱空間。Redis 鍵和通道 ID 以 <redisNamespace>: 為字首。 * flushMode:允許指定何時將資料寫入 Redis。預設值是僅在 SessionRepository 上呼叫 save 時寫入。值為 FlushMode.IMMEDIATE 會盡快寫入 Redis。

自定義 RedisSerializer

您可以透過建立名為 springSessionDefaultRedisSerializer 的 bean(實現 RedisSerializer<Object>)來自定義序列化。

在 Redis 中檢視會話

安裝 redis-cli 後,您可以使用 redis-cli 檢查 Redis 中的值。例如,您可以在終端視窗中輸入以下命令。

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
1 此鍵的字尾是 Spring Session 的會話識別符號。

您還可以使用 hkeys 命令檢視每個會話的屬性。以下示例顯示瞭如何操作。

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"

使用 RedisIndexedSessionRepository

RedisIndexedSessionRepository 是一個使用 Spring Data 的 RedisOperations 實現的 SessionRepository。在 Web 環境中,這通常與 SessionRepositoryFilter 結合使用。此實現透過 SessionMessageListener 支援 SessionDestroyedEventSessionCreatedEvent 的釋出。

例項化 RedisIndexedSessionRepository

您可以在以下清單中看到如何建立新例項的典型示例。

RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository<? extends Session> repository = new RedisIndexedSessionRepository(redisTemplate);

有關如何建立 RedisConnectionFactory 的更多資訊,請參閱 Spring Data Redis 參考。

使用 @EnableRedisHttpSession(enableIndexingAndEvents = true)

在 Web 環境中,建立新的 RedisIndexedSessionRepository 的最簡單方法是使用 @EnableRedisHttpSession(enableIndexingAndEvents = true)。您可以在 示例和指南(從這裡開始) 中找到完整的示例用法。您可以使用以下屬性自定義配置。

  • enableIndexingAndEvents:是否使用 RedisIndexedSessionRepository 而不是 RedisSessionRepository。預設值為 false

  • maxInactiveIntervalInSeconds:會話過期前的時間量,以秒為單位。

  • redisNamespace:允許為會話配置應用程式特定的名稱空間。Redis 鍵和通道 ID 以 <redisNamespace>: 為字首。

  • flushMode:允許指定何時將資料寫入 Redis。預設值是僅在 SessionRepository 上呼叫 save 時寫入。值為 FlushMode.IMMEDIATE 會盡快寫入 Redis。

自定義 RedisSerializer

您可以透過建立名為 springSessionDefaultRedisSerializer 的 bean(實現 RedisSerializer<Object>)來自定義序列化。

Redis TaskExecutor

RedisIndexedSessionRepository 透過使用 RedisMessageListenerContainer 訂閱從 Redis 接收事件。您可以透過建立名為 springSessionRedisTaskExecutor 的 bean、springSessionRedisSubscriptionExecutor 的 bean 或兩者來自定義這些事件的分發方式。有關配置 Redis 任務執行器的更多詳細資訊,請參閱 此處

儲存詳情

以下部分概述了每個操作如何更新 Redis。以下示例顯示了建立新會話的示例。

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
	maxInactiveInterval 1800 \
	lastAccessedTime 1404360000000 \
	sessionAttr:attrName someAttrValue \
	sessionAttr:attrName2 someAttrValue2
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100

後續部分描述了詳細資訊。

儲存會話

每個會話都作為 Hash 儲存在 Redis 中。每個會話都使用 HMSET 命令進行設定和更新。以下示例顯示了每個會話的儲存方式。

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
	maxInactiveInterval 1800 \
	lastAccessedTime 1404360000000 \
	sessionAttr:attrName someAttrValue \
	sessionAttr:attrName2 someAttrValue2

在前面的示例中,關於會話,以下語句是真實的。

  • 會話 ID 是 33fdd1b6-b496-4b33-9f7d-df96679d32fe。

  • 會話創建於 1404360000000(自 1970 年 1 月 1 日 GMT 午夜以來的毫秒數)。

  • 會話在 1800 秒(30 分鐘)後過期。

  • 會話上次訪問於 1404360000000(自 1970 年 1 月 1 日 GMT 午夜以來的毫秒數)。

  • 會話有兩個屬性。第一個是 attrName,值為 someAttrValue。第二個會話屬性名為 attrName2,值為 someAttrValue2

最佳化寫入

RedisIndexedSessionRepository 管理的 Session 例項會跟蹤已更改的屬性,並且只更新這些屬性。這意味著,如果一個屬性寫入一次並讀取多次,我們只需要寫入該屬性一次。例如,假設前面的清單中的 attrName2 會話屬性已更新。儲存時將執行以下命令。

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue

會話過期

每個會話都使用 EXPIRE 命令關聯一個過期時間,基於 Session.getMaxInactiveInterval()。以下示例顯示了一個典型的 EXPIRE 命令。

EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100

請注意,過期時間設定在會話實際過期後五分鐘。這是必要的,以便在會話過期時可以訪問會話的值。會話本身設定的過期時間在會話實際過期後五分鐘,以確保它被清理,但僅在我們執行任何必要的處理之後。

SessionRepository.findById(String) 方法確保不返回過期的會話。這意味著在使用會話之前,您無需檢查過期。

Spring Session 依賴於 Redis 的刪除和過期 鍵空間通知 來分別觸發 SessionDeletedEventSessionExpiredEventSessionDeletedEventSessionExpiredEvent 確保與 Session 關聯的資源被清理。例如,當您使用 Spring Session 的 WebSocket 支援時,Redis 過期或刪除事件會觸發與會話關聯的任何 WebSocket 連線關閉。

過期時間不是直接在會話鍵本身上跟蹤的,因為這意味著會話資料將不再可用。相反,使用了特殊的會話過期鍵。在前面的示例中,過期鍵如下所示。

APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800

當會話過期鍵被刪除或過期時,鍵空間通知會觸發對實際會話的查詢,並觸發 SessionDestroyedEvent

完全依賴 Redis 過期的缺點是,如果鍵未被訪問,Redis 無法保證何時觸發過期事件。具體來說,Redis 用於清理過期鍵的後臺任務是低優先順序任務,可能不會觸發鍵過期。有關更多詳細資訊,請參閱 Redis 文件中的 過期事件的時序 部分。

為了規避過期事件不保證發生的實際情況,我們可以確保每個鍵在其預期過期時都被訪問。這意味著,如果鍵上的 TTL 已過期,當我們嘗試訪問該鍵時,Redis 會刪除該鍵並觸發過期事件。

因此,每個會話過期時間也以最接近的分鐘進行跟蹤。這允許後臺任務訪問可能過期的會話,以確保 Redis 過期事件以更確定的方式觸發。以下示例顯示了這些事件。

SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100

然後後臺任務使用這些對映來顯式請求每個鍵。透過訪問鍵而不是刪除它,我們確保 Redis 僅在 TTL 過期時才為我們刪除鍵。

我們不會顯式刪除鍵,因為在某些情況下,可能會出現競爭條件,錯誤地將鍵標識為已過期而實際上沒有過期。除了使用分散式鎖(這會降低我們的效能)之外,沒有辦法確保過期對映的一致性。透過簡單地訪問鍵,我們確保只有當該鍵上的 TTL 過期時才刪除該鍵。

SessionDeletedEventSessionExpiredEvent

SessionDeletedEventSessionExpiredEvent 都是 SessionDestroyedEvent 的型別。

RedisIndexedSessionRepository 支援在 Session 被刪除時觸發 SessionDeletedEvent,或者在 Session 過期時觸發 SessionExpiredEvent。這對於確保與 Session 關聯的資源得到正確清理是必要的。

例如,與 WebSocket 整合時,SessionDestroyedEvent 負責關閉任何活動的 WebSocket 連線。

透過 SessionMessageListener 提供 SessionDeletedEventSessionExpiredEvent 的觸發,該監聽器監聽 Redis 鍵空間事件。為了使其工作,需要啟用 Redis 鍵空間事件用於通用命令和過期事件。以下示例顯示瞭如何操作。

redis-cli config set notify-keyspace-events Egx

如果您使用 @EnableRedisHttpSession(enableIndexingAndEvents = true),則會自動管理 SessionMessageListener 並啟用必要的 Redis 鍵空間事件。但是,在安全的 Redis 環境中,config 命令是停用的。這意味著 Spring Session 無法為您配置 Redis 鍵空間事件。要停用自動配置,請將 ConfigureRedisAction.NO_OP 作為 bean 新增。

例如,對於 Java 配置,您可以使用以下內容。

@Bean
ConfigureRedisAction configureRedisAction() {
	return ConfigureRedisAction.NO_OP;
}

在 XML 配置中,您可以使用以下內容。

<util:constant
	static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>

使用 SessionCreatedEvent

建立會話時,會向 Redis 傳送一個事件,通道 ID 為 spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe,其中 33fdd1b6-b496-4b33-9f7d-df96679d32fe 是會話 ID。事件的主體是建立的會話。

如果註冊為 MessageListener(預設),RedisIndexedSessionRepository 然後將 Redis 訊息轉換為 SessionCreatedEvent

在 Redis 中檢視會話

安裝 redis-cli 後,您可以使用 redis-cli 檢查 Redis 中的值。例如,您可以在終端中輸入以下內容。

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
2) "spring:session:expirations:1418772300000" (2)
1 此鍵的字尾是 Spring Session 的會話識別符號。
2 此鍵包含在 1418772300000 時應刪除的所有會話 ID。

您還可以檢視每個會話的屬性。以下示例顯示瞭如何操作。

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"

使用 ReactiveRedisSessionRepository

ReactiveRedisSessionRepository 是一個使用 Spring Data 的 ReactiveRedisOperations 實現的 ReactiveSessionRepository。在 Web 環境中,這通常與 WebSessionStore 結合使用。

例項化 ReactiveRedisSessionRepository

以下示例顯示瞭如何建立新例項。

// ... create and configure connectionFactory and serializationContext ...

ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>(connectionFactory,
		serializationContext);

ReactiveSessionRepository<? extends Session> repository = new ReactiveRedisSessionRepository(redisTemplate);

有關如何建立 ReactiveRedisConnectionFactory 的更多資訊,請參閱 Spring Data Redis 參考。

使用 @EnableRedisWebSession

在 Web 環境中,建立新的 ReactiveRedisSessionRepository 的最簡單方法是使用 @EnableRedisWebSession。您可以使用以下屬性自定義配置。

  • maxInactiveIntervalInSeconds:會話過期前的時間量,以秒為單位。

  • redisNamespace:允許為會話配置應用程式特定的名稱空間。Redis 鍵和通道 ID 以 <redisNamespace>: 為字首。

  • flushMode:允許指定何時將資料寫入 Redis。預設值是僅在 ReactiveSessionRepository 上呼叫 save 時寫入。值為 FlushMode.IMMEDIATE 會盡快寫入 Redis。

最佳化寫入

ReactiveRedisSessionRepository 管理的 Session 例項會跟蹤已更改的屬性,並且只更新這些屬性。這意味著,如果一個屬性寫入一次並讀取多次,我們只需要寫入該屬性一次。

在 Redis 中檢視會話

安裝 redis-cli 後,您可以使用 redis-cli 檢查 Redis 中的值。例如,您可以在終端視窗中輸入以下命令。

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
1 此鍵的字尾是 Spring Session 的會話識別符號。

您還可以使用 hkeys 命令檢視每個會話的屬性。以下示例顯示瞭如何操作。

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"

使用 MapSessionRepository

MapSessionRepository 允許將會話持久化到 Map 中,其中鍵是 Session ID,值是 Session。您可以將此實現與 ConcurrentHashMap 一起用作測試或便利機制。或者,您可以將其與分散式 Map 實現一起使用。例如,它可以與 Hazelcast 一起使用。

例項化 MapSessionRepository

以下示例顯示瞭如何建立新例項。

SessionRepository<? extends Session> repository = new MapSessionRepository(new ConcurrentHashMap<>());

使用 Spring Session 和 Hazlecast

Hazelcast 示例 是一個完整的應用程式,演示瞭如何將 Spring Session 與 Hazelcast 結合使用。

要執行它,請使用以下命令。

	./gradlew :samples:hazelcast:tomcatRun

Hazelcast Spring 示例 是一個完整的應用程式,演示瞭如何將 Spring Session 與 Hazelcast 和 Spring Security 結合使用。

它包括示例 Hazelcast MapListener 實現,支援觸發 SessionCreatedEventSessionDeletedEventSessionExpiredEvent

要執行它,請使用以下命令。

	./gradlew :samples:hazelcast-spring:tomcatRun

使用 ReactiveMapSessionRepository

ReactiveMapSessionRepository 允許將 Session 持久化到 Map 中,其中鍵是 Session ID,值是 Session。您可以將此實現與 ConcurrentHashMap 一起用作測試或便利機制。或者,您可以將其與分散式 Map 實現一起使用,前提是提供的 Map 必須是非阻塞的。

使用 JdbcIndexedSessionRepository

JdbcIndexedSessionRepository 是一個 SessionRepository 實現,它使用 Spring 的 JdbcOperations 將會話儲存在關係資料庫中。在 Web 環境中,這通常與 SessionRepositoryFilter 結合使用。請注意,此實現不支援釋出會話事件。

例項化 JdbcIndexedSessionRepository

以下示例顯示瞭如何建立新例項。

JdbcTemplate jdbcTemplate = new JdbcTemplate();

// ... configure jdbcTemplate ...

TransactionTemplate transactionTemplate = new TransactionTemplate();

// ... configure transactionTemplate ...

SessionRepository<? extends Session> repository = new JdbcIndexedSessionRepository(jdbcTemplate,
		transactionTemplate);

有關如何建立和配置 JdbcTemplatePlatformTransactionManager 的更多資訊,請參閱 Spring Framework 參考文件

使用 @EnableJdbcHttpSession

在 Web 環境中,建立新的 JdbcIndexedSessionRepository 的最簡單方法是使用 @EnableJdbcHttpSession。您可以在 示例和指南(從這裡開始) 中找到完整的示例用法。您可以使用以下屬性自定義配置。

  • tableName:Spring Session 用於儲存會話的資料庫表的名稱。

  • maxInactiveIntervalInSeconds:會話過期前的時間量,以秒為單位。

自定義 LobHandler

您可以透過建立名為 springSessionLobHandler 並實現 LobHandler 的 bean 來定製 BLOB 處理。

自定義 ConversionService

您可以透過提供 ConversionService 例項來自定義會話的預設序列化和反序列化。在典型的 Spring 環境中,預設的 ConversionService bean(名為 conversionService)會自動被選中並用於序列化和反序列化。但是,您可以透過提供名為 springSessionConversionService 的 bean 來覆蓋預設的 ConversionService

儲存詳情

預設情況下,此實現使用 SPRING_SESSIONSPRING_SESSION_ATTRIBUTES 表來儲存會話。請注意,您可以自定義表名,如前所述。在這種情況下,用於儲存屬性的表名使用提供的表名加上 _ATTRIBUTES 字尾。如果需要進一步的自定義,您可以使用 set*Query setter 方法自定義儲存庫使用的 SQL 查詢。在這種情況下,您需要手動配置 sessionRepository bean。

由於各種資料庫供應商之間的差異,尤其是在儲存二進位制資料方面,請務必使用特定於您的資料庫的 SQL 指令碼。大多數主要資料庫供應商的指令碼都打包為 org/springframework/session/jdbc/schema-*.sql,其中 * 是目標資料庫型別。

例如,對於 PostgreSQL,您可以使用以下 schema 指令碼。

CREATE TABLE SPRING_SESSION (
	PRIMARY_ID CHAR(36) NOT NULL,
	SESSION_ID CHAR(36) NOT NULL,
	CREATION_TIME BIGINT NOT NULL,
	LAST_ACCESS_TIME BIGINT NOT NULL,
	MAX_INACTIVE_INTERVAL INT NOT NULL,
	EXPIRY_TIME BIGINT NOT NULL,
	PRINCIPAL_NAME VARCHAR(100),
	CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
	SESSION_PRIMARY_ID CHAR(36) NOT NULL,
	ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
	ATTRIBUTE_BYTES BYTEA NOT NULL,
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);

對於 MySQL 資料庫,您可以使用以下指令碼。

CREATE TABLE SPRING_SESSION (
	PRIMARY_ID CHAR(36) NOT NULL,
	SESSION_ID CHAR(36) NOT NULL,
	CREATION_TIME BIGINT NOT NULL,
	LAST_ACCESS_TIME BIGINT NOT NULL,
	MAX_INACTIVE_INTERVAL INT NOT NULL,
	EXPIRY_TIME BIGINT NOT NULL,
	PRINCIPAL_NAME VARCHAR(100),
	CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
	SESSION_PRIMARY_ID CHAR(36) NOT NULL,
	ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
	ATTRIBUTE_BYTES BLOB NOT NULL,
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

事務管理

JdbcIndexedSessionRepository 中的所有 JDBC 操作都以事務方式執行。事務的傳播行為設定為 REQUIRES_NEW,以避免由於與現有事務的干擾而導致的意外行為(例如,在已參與只讀事務的執行緒中執行 save 操作)。

使用 HazelcastIndexedSessionRepository

HazelcastIndexedSessionRepository 是一個 SessionRepository 實現,它將會話儲存在 Hazelcast 的分散式 IMap 中。在 Web 環境中,這通常與 SessionRepositoryFilter 結合使用。

例項化 HazelcastIndexedSessionRepository

以下示例顯示瞭如何建立新例項。

Config config = new Config();

// ... configure Hazelcast ...

HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);

HazelcastIndexedSessionRepository repository = new HazelcastIndexedSessionRepository(hazelcastInstance);

有關如何建立和配置 Hazelcast 例項的更多資訊,請參閱 Hazelcast 文件

使用 @EnableHazelcastHttpSession

要使用 Hazelcast 作為 SessionRepository 的後端源,您可以將 @EnableHazelcastHttpSession 註解新增到 @Configuration 類中。這樣做擴充套件了 @EnableSpringHttpSession 註解提供的功能,但在 Hazelcast 中為您建立了 SessionRepository。您必須提供單個 HazelcastInstance bean 才能使配置生效。您可以在 示例和指南(從這裡開始) 中找到完整的配置示例。

基本自定義

您可以使用 @EnableHazelcastHttpSession 上的以下屬性來自定義配置。

  • maxInactiveIntervalInSeconds:會話過期前的時間量,以秒為單位。預設值為 1800 秒(30 分鐘)。

  • sessionMapName:Hazelcast 中用於儲存會話資料的分散式 Map 的名稱。

會話事件

使用 MapListener 響應新增到分散式 Map、被逐出和從分散式 Map 中刪除的條目會導致這些事件透過 ApplicationEventPublisher 觸發 SessionCreatedEventSessionExpiredEventSessionDeletedEvent 事件(分別)。

儲存詳情

會話儲存在 Hazelcast 的分散式 IMap 中。IMap 介面方法用於 get()put() 會話。此外,values() 方法支援 FindByIndexNameSessionRepository#findByIndexNameAndIndexValue 操作,以及適當的 ValueExtractor(需要註冊到 Hazelcast)。有關此配置的更多詳細資訊,請參閱 Hazelcast Spring 示例IMap 中會話的過期由 Hazelcast 對條目在 put()IMap 時設定生存時間的支援來處理。閒置時間超過生存時間的條目(會話)將自動從 IMap 中刪除。

您不需要在 Hazelcast 配置中為 IMap 配置任何設定,例如 max-idle-secondstime-to-live-seconds

請注意,如果您使用 Hazelcast 的 MapStore 來持久化您的會話 IMap,則從 MapStore 重新載入會話時會應用以下限制。

  • 重新載入會觸發 EntryAddedListener,導致 SessionCreatedEvent 重新發布。

  • 重新載入會話時,如果使用給定 IMap 的預設 TTL,則會導致會話失去其原始 TTL。

使用 CookieSerializer

CookieSerializer 負責定義會話 cookie 的寫入方式。Spring Session 提供了使用 DefaultCookieSerializer 的預設實現。

CookieSerializer 作為 bean 暴露

當您使用 @EnableRedisHttpSession 等配置時,將 CookieSerializer 作為 Spring bean 暴露會增強現有配置。

以下示例展示瞭如何實現:

	@Bean
	public CookieSerializer cookieSerializer() {
		DefaultCookieSerializer serializer = new DefaultCookieSerializer();
		serializer.setCookieName("JSESSIONID"); (1)
		serializer.setCookiePath("/"); (2)
		serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); (3)
		return serializer;
	}
1 我們將 Cookie 的名稱定製為 JSESSIONID
2 我們將 Cookie 的路徑定製為 /(而不是預設的上下文根)。
3 我們將域名模式(正則表示式)定製為 `^.?\\.(\\w\\.[a-z]+)$`。這允許在多個域和應用程式之間共享會話。如果正則表示式不匹配,則不設定任何域並使用現有域。如果正則表示式匹配,則第一個分組用作域。這意味著對 child.example.com 的請求將域設定為 `example.com`。然而,對 localhost:8080/192.168.1.100:8080/ 的請求則不設定 Cookie,因此在開發中仍然有效,無需對生產環境進行任何更改。
您應該只匹配有效的域名字元,因為域名會反映在響應中。這樣做可以防止惡意使用者執行諸如 HTTP 響應拆分之類的攻擊。

自定義 CookieSerializer

您可以使用 DefaultCookieSerializer 上的以下任何配置選項來自定義會話 cookie 的寫入方式。

  • cookieName:要使用的 Cookie 名稱。預設值:SESSION

  • useSecureCookie:指定是否應使用安全 Cookie。預設值:在建立時使用 HttpServletRequest.isSecure() 的值。

  • cookiePath:Cookie 的路徑。預設值:上下文根。

  • cookieMaxAge:指定在會話建立時要設定的 Cookie 的最大年齡。預設值:-1,表示當瀏覽器關閉時應刪除 Cookie。

  • jvmRoute:指定一個字尾,該字尾將附加到會話 ID 幷包含在 Cookie 中。用於識別要路由到哪個 JVM 以實現會話親和性。對於某些實現(即 Redis),此選項不提供效能優勢。但是,它可以幫助跟蹤特定使用者的日誌。

  • domainName:允許指定用於 Cookie 的特定域名。此選項易於理解,但通常需要在開發和生產環境之間進行不同的配置。請參閱 domainNamePattern 作為替代方案。

  • domainNamePattern:一個不區分大小寫的模式,用於從 HttpServletRequest#getServerName() 中提取域名。該模式應提供一個用於提取 Cookie 域值的分組。如果正則表示式不匹配,則不設定任何域並使用現有域。如果正則表示式匹配,則第一個分組用作域。

  • sameSiteSameSite Cookie 指令的值。要停用 SameSite Cookie 指令的序列化,您可以將此值設定為 null。預設值:Lax

您應該只匹配有效的域名字元,因為域名會反映在響應中。這樣做可以防止惡意使用者執行諸如 HTTP 響應拆分之類的攻擊。

自定義 SessionRepository

實現自定義 SessionRepository API 應該是一項相當直接的任務。將自定義實現與 @EnableSpringHttpSession 支援相結合,可以重用現有的 Spring Session 配置設施和基礎設施。然而,有幾個方面值得更密切的考慮。

在 HTTP 請求的生命週期中,HttpSession 通常會被持久化到 SessionRepository 兩次。第一次持久化操作是為了確保客戶端在訪問會話 ID 後立即可以使用會話,並且在會話提交後也需要寫入,因為可能會對會話進行進一步的修改。考慮到這一點,我們通常建議 SessionRepository 實現跟蹤更改,以確保只儲存增量。這在高度併發的環境中尤為重要,其中多個請求操作相同的 HttpSession,因此會導致競爭條件,請求會覆蓋彼此對會話屬性的更改。Spring Session 提供的所有 SessionRepository 實現都使用上述方法持久化會話更改,並可在您實現自定義 SessionRepository 時用作指導。

請注意,對於實現自定義 ReactiveSessionRepository,也適用相同的建議。在這種情況下,您應該使用 @EnableSpringWebSession

© . This site is unofficial and not affiliated with VMware.