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 我們建立一個具有泛型類型 SessionRepository 實例 S,其擴充 Session。泛型類型在我們的類別中定義。
2 我們使用 SessionRepository 建立一個新的 Session,並將其指派給類型為 S 的變數。
3 我們與 Session 互動。在我們的範例中,我們示範將 User 儲存到 Session
4 我們現在儲存 Session。這就是我們需要泛型類型 S 的原因。SessionRepository 僅允許儲存使用相同的 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 我們建立一個具有泛型類型 SessionRepository 實例 S,其擴充 Session。泛型類型在我們的類別中定義。
2 我們使用 SessionRepository 建立一個新的 Session,並將其指派給類型為 S 的變數。
3 我們與 Session 互動。在我們的範例中,我們示範更新 Session 在過期之前可以處於非活動狀態的時間量。
4 我們現在儲存 Session。這就是我們需要泛型類型 S 的原因。SessionRepository 僅允許儲存使用相同的 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。預設值僅在 saveSessionRepository 上叫用時。值為 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。預設值僅在 saveSessionRepository 上叫用時。值為 FlushMode.IMMEDIATE 會盡快寫入 Redis。

自訂 RedisSerializer

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

Redis TaskExecutor

RedisIndexedSessionRepository 已訂閱以使用 RedisMessageListenerContainer 接收來自 Redis 的事件。您可以透過建立名為 springSessionRedisTaskExecutor 的 bean、bean springSessionRedisSubscriptionExecutor 或兩者來客製化這些事件的分派方式。您可以在 此處 找到有關組態 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 的刪除和過期 keyspace 通知 來觸發 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

當會期過期金鑰刪除或過期時,keyspace 通知會觸發實際會期的查閱,並觸發 SessionDestroyedEvent

僅依賴 Redis 過期的一個問題是,如果金鑰尚未存取,Redis 無法保證何時觸發過期事件。具體而言,Redis 用於清除過期金鑰的背景工作是低優先順序工作,可能不會觸發金鑰過期。如需其他詳細資訊,請參閱 Redis 文件中的 過期事件的時序 章節。

為了規避無法保證會發生過期事件的事實,我們可以確保在預期到期時存取每個金鑰。這表示,如果金鑰的 TTL 過期,Redis 會在我們嘗試存取金鑰時移除金鑰並觸發過期事件。

因此,每個會期過期時間也會追蹤到最接近的分鐘。這讓背景工作可以存取可能過期的會期,以確保 Redis 過期事件以更具決定性的方式觸發。以下範例顯示這些事件

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

然後,背景工作會使用這些對應來明確要求每個金鑰。透過存取金鑰而不是刪除金鑰,我們確保僅在該金鑰的 TTL 過期時,Redis 才會為我們刪除金鑰。

我們不會明確刪除金鑰,因為在某些情況下,可能會發生競爭情況,錯誤地將金鑰識別為已過期,但實際上並未過期。除了使用分散式鎖定 (這會降低我們的效能) 之外,沒有任何方法可以確保過期對應的一致性。透過僅存取金鑰,我們確保僅在該金鑰的 TTL 過期時才移除金鑰。

SessionDeletedEventSessionExpiredEvent

SessionDeletedEventSessionExpiredEvent 都是 SessionDestroyedEvent 的類型。

Session 刪除時,RedisIndexedSessionRepository 支援觸發 SessionDeletedEvent;當 Session 過期時,支援觸發 SessionExpiredEvent。這是必要的,以確保與 Session 相關聯的資源已正確清除。

例如,與 WebSockets 整合時,SessionDestroyedEvent 負責關閉任何作用中的 WebSocket 連線。

觸發 SessionDeletedEventSessionExpiredEvent 可透過 SessionMessageListener 取得,後者會監聽 Redis Keyspace 事件。為了使其運作,需要啟用 Generic 命令和 Expired 事件的 Redis Keyspace 事件。以下範例顯示如何執行此操作

redis-cli config set notify-keyspace-events Egx

如果您使用 @EnableRedisHttpSession(enableIndexingAndEvents = true),則會自動完成管理 SessionMessageListener 和啟用必要的 Redis Keyspace 事件。但是,在安全的 Redis 環境中,config 命令已停用。這表示 Spring Session 無法為您組態 Redis Keyspace 事件。若要停用自動組態,請新增 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>: 字首 q 開頭。

  • flushMode:允許指定何時將資料寫入 Redis。預設值僅在 saveReactiveSessionRepository 上叫用時。值為 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,其中金鑰為 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 允許在 Map 中持久化 Session,其中金鑰為 Session ID,值為 Session。您可以搭配 ConcurrentHashMap 使用此實作作為測試或便利機制。或者,您可以將其與分散式 Map 實作搭配使用,但要求提供的 Map 必須是非封鎖的。

使用 JdbcIndexedSessionRepository

JdbcIndexedSessionRepositorySessionRepository 實作,使用 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 的 bean (實作 LobHandler) 來自訂 BLOB 處理。

自訂 ConversionService

您可以藉由提供 ConversionService 實例,自訂 Session 的預設序列化和反序列化行為。在典型的 Spring 環境中,預設的 ConversionService Bean(名稱為 conversionService)會自動被選取並用於序列化和反序列化。然而,您可以透過提供一個名為 springSessionConversionService 的 Bean 來覆寫預設的 ConversionService

儲存細節

預設情況下,此實作使用 SPRING_SESSIONSPRING_SESSION_ATTRIBUTES 資料表來儲存 Session。請注意,您可以自訂資料表名稱,如前所述。在這種情況下,用於儲存屬性的資料表名稱會使用提供的資料表名稱加上 _ATTRIBUTES 後綴。如果需要進一步的自訂,您可以透過使用 set*Query Setter 方法來自訂 Repository 使用的 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 中儲存 Session。在 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:Session 過期前的時間量,以秒為單位。預設值為 1800 秒(30 分鐘)

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

Session 事件

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

儲存細節

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

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

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

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

  • 重新載入針對給定的 IMap 使用預設 TTL,導致 Session 遺失其原始 TTL

使用 CookieSerializer

CookieSerializer 負責定義如何寫入 Session 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 的路徑為 /(而不是預設的 Context Root)。
3 我們自訂網域名稱模式(正規表示式)為 ^.?\\.(\\w\\.[a-z]+)$。這允許跨網域和應用程式共用 Session。如果正規表示式不符,則不設定網域並使用現有的網域。如果正規表示式相符,則第一個群組會用作網域。這表示對 child.example.com 的請求會將網域設定為 example.com。但是,對 localhost:8080/192.168.1.100:8080/ 的請求會使 Cookie 保持未設定狀態,因此,在開發中仍然可以運作,而無需對生產環境進行任何變更。
您應該僅比對有效的網域字元,因為網域名稱會反映在回應中。這樣做可以防止惡意使用者執行諸如 HTTP 回應分割 之類的攻擊。

自訂 CookieSerializer

您可以使用 DefaultCookieSerializer 上的以下任何配置選項來自訂 Session Cookie 的寫入方式。

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

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

  • cookiePath:Cookie 的路徑。預設值:Context Root。

  • cookieMaxAge:指定在建立 Session 時要設定的 Cookie 的最大存活時間。預設值:-1,表示在瀏覽器關閉時應移除 Cookie。

  • jvmRoute:指定要附加到 Session ID 並包含在 Cookie 中的後綴。用於識別要路由到哪個 JVM 以進行 Session Affinity。對於某些實作(即 Redis),此選項不提供效能優勢。但是,它可以幫助追蹤特定使用者的記錄。

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

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

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

您應該僅比對有效的網域字元,因為網域名稱會反映在回應中。這樣做可以防止惡意使用者執行諸如 HTTP 回應分割 之類的攻擊。

自訂 SessionRepository

實作自訂的 SessionRepository API 應該是一項相當簡單的任務。將自訂實作與 @EnableSpringHttpSession 支援結合使用,可讓您重複使用現有的 Spring Session 配置設施和基礎架構。但是,有幾個方面值得更仔細地考慮。

在 HTTP 請求的生命週期中,HttpSession 通常會持久化到 SessionRepository 兩次。第一次持久化操作是為了確保在用戶端可以存取 Session ID 後,Session 即可供用戶端使用,並且在 Session 提交後也必須寫入,因為可能會對 Session 進行進一步的修改。記住這一點,我們通常建議 SessionRepository 實作追蹤變更,以確保僅儲存差異。這在高度並發的環境中尤其重要,在這些環境中,多個請求對同一個 HttpSession 進行操作,因此會導致競爭條件,請求會覆寫彼此對 Session 屬性的變更。Spring Session 提供的所有 SessionRepository 實作都使用所描述的方法來持久化 Session 變更,並且可以在您實作自訂 SessionRepository 時用作指導。

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