API 文件
您可以瀏覽完整的 Javadoc 線上文件。關鍵的 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 | 我們建立一個帶有泛型 S ,擴充套件自 Session 的 SessionRepository 例項。泛型型別在我們類中定義。 |
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 | 我們建立一個帶有泛型 S ,擴充套件自 Session 的 SessionRepository 例項。泛型型別在我們類中定義。 |
2 | 我們使用我們的 SessionRepository 建立一個新的 Session 並將其賦值給一個型別為 S 的變數。 |
3 | 我們與 Session 互動。在我們的示例中,我們演示瞭如何更新 Session 在過期前可以處於非活動狀態的時長。 |
4 | 現在我們儲存 Session 。這就是為什麼我們需要泛型 S 。SessionRepository 只允許儲存使用同一個 SessionRepository 建立或檢索的 Session 例項。這允許 SessionRepository 進行特定實現的最佳化(即只寫入已更改的屬性)。Session 儲存時,最後訪問時間會自動更新。 |
5 | 我們從 SessionRepository 檢索 Session 。如果 Session 已過期,結果將為 null。 |
使用 SessionRepository
一個 SessionRepository
負責建立、檢索和持久化 Session
例項。
如果可能,您不應直接與 SessionRepository
或 Session
互動。相反,開發者應優先透過 HttpSession
和 WebSocket 整合間接與 SessionRepository
和 Session
互動。
使用 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
例項。
如果可能,您不應直接與 ReactiveSessionRepository
或 Session
互動。相反,您應優先透過 WebSession 整合間接與 ReactiveSessionRepository
和 Session
互動。
使用 @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
是一個 SessionRepository
,它使用 Spring Data 的 RedisOperations
實現。在 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。
在 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
是一個 SessionRepository
,它使用 Spring Data 的 RedisOperations
實現。在 web 環境中,這通常與 SessionRepositoryFilter
結合使用。此實現透過 SessionMessageListener
支援 SessionDestroyedEvent
和 SessionCreatedEvent
。
例項化一個 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。
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
後續章節描述了具體細節。
儲存會話
每個會話在 Redis 中以一個 Hash
的形式儲存。每個會話透過使用 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(自 GMT 1970 年 1 月 1 日午夜以來的毫秒數)。
-
會話在 1800 秒(30 分鐘)後過期。
-
會話最後訪問於 1404360000000(自 GMT 1970 年 1 月 1 日午夜以來的毫秒數)。
-
該會話有兩個屬性。第一個是
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 的刪除和過期鍵空間通知來分別觸發 SessionDeletedEvent
和 SessionExpiredEvent
。SessionDeletedEvent
或 SessionExpiredEvent
確保與 Session
相關的資源被清理。例如,當您使用 Spring Session 的 WebSocket 支援時,Redis 的過期或刪除事件會觸發與該會話相關的任何 WebSocket 連線關閉。
過期時間並非直接在 session key 本身上跟蹤,因為這意味著會話資料將不再可用。相反,使用了一個特殊的 session expires key。在前面的示例中, expires key 如下所示
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe "" EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
當一個 session expires key 被刪除或過期時,鍵空間通知會觸發對實際會話的查詢,並觸發一個 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 過期時才將其移除。 |
SessionDeletedEvent
和 SessionExpiredEvent
SessionDeletedEvent
和 SessionExpiredEvent
都是 SessionDestroyedEvent
的型別。
RedisIndexedSessionRepository
支援在 Session
被刪除時觸發 SessionDeletedEvent
,或在 Session
過期時觸發 SessionExpiredEvent
。這對於確保與 Session
相關的資源得到正確清理是必要的。
例如,在與 WebSockets 整合時,SessionDestroyedEvent
負責關閉任何活動的 WebSocket 連線。
觸發 SessionDeletedEvent
或 SessionExpiredEvent
透過 SessionMessageListener
實現,它監聽 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
是一個 ReactiveSessionRepository
,它使用 Spring Data 的 ReactiveRedisOperations
實現。在 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。
在 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
允許將 Session
持久化到 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
實現,支援觸發 SessionCreatedEvent
、SessionDeletedEvent
和 SessionExpiredEvent
事件。
要執行它,請使用以下命令
./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);
有關如何建立和配置 JdbcTemplate
和 PlatformTransactionManager
的更多資訊,請參閱Spring Framework 參考文件。
使用 @EnableJdbcHttpSession
在 web 環境中,建立新的 JdbcIndexedSessionRepository
最簡單的方法是使用 @EnableJdbcHttpSession
。您可以在 示例與指南 (由此開始) 中找到完整的示例用法。您可以使用以下屬性自定義配置
-
tableName: Spring Session 用於儲存會話的資料庫表名
-
maxInactiveIntervalInSeconds: 會話過期前的時間量,以秒為單位
儲存詳情
預設情況下,此實現使用 SPRING_SESSION
和 SPRING_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;
使用 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
中條目被新增、逐出和移除事件,會導致這些事件(分別)透過 ApplicationEventPublisher
觸發釋出 SessionCreatedEvent
、SessionExpiredEvent
和 SessionDeletedEvent
事件。
儲存詳情
會話儲存在 Hazelcast 的分散式 IMap
中。IMap
介面方法用於 get()
和 put()
會話。此外,values()
方法支援一個 FindByIndexNameSessionRepository#findByIndexNameAndIndexValue
操作,並配合適當的 ValueExtractor
(需要向 Hazelcast 註冊)。有關此配置的更多詳情,請參閱 Hazelcast Spring Sample。IMap
中會話的過期由 Hazelcast 支援在條目被 put()
到 IMap
時設定生存時間(TTL)來處理。閒置時間超過生存時間的條目(會話)會自動從 IMap
中移除。
您應該無需在 Hazelcast 配置中為 IMap
配置任何設定,例如 max-idle-seconds
或 time-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 Response Splitting 等攻擊。 |
定製 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 域的值。如果正則表示式不匹配,則不設定域,並使用現有域。如果正則表示式匹配,則第一個分組用作域。 -
sameSite
:SameSite
cookie 指令的值。要停用SameSite
cookie 指令的序列化,您可以將此值設定為null
。預設值:Lax
您應僅匹配有效的域名字元,因為域名會反映在響應中。這樣做可以防止惡意使用者執行 HTTP Response Splitting 等攻擊。 |
定製 SessionRepository
實現自定義的SessionRepository
API 應該是一個相當簡單的任務。將自定義實現與@EnableSpringHttpSession
支援結合使用,可以重用現有的 Spring Session 配置功能和基礎設施。然而,有幾個方面值得仔細考慮。
在 HTTP 請求的生命週期中,HttpSession
通常會持久化到 SessionRepository
兩次。第一次持久化操作是為了確保會話在客戶端一旦獲取到會話 ID 就能使用,並且在會話提交後也需要寫入,因為可能會對會話進行進一步的修改。考慮到這一點,我們通常建議 SessionRepository
實現跟蹤更改以確保只儲存增量。這在高度併發的環境中尤為重要,其中多個請求操作同一個 HttpSession
,因此會導致競爭條件,請求相互覆蓋對方對會話屬性的更改。Spring Session 提供的所有 SessionRepository
實現都使用所述方法來持久化會話更改,並且可以在您實現自定義 SessionRepository
時作為指導。
請注意,相同的建議也適用於實現自定義的ReactiveSessionRepository
。在這種情況下,您應該使用@EnableSpringWebSession
。