You can browse the complete Javadoc online. The key APIs are described below:
A Session
is a simplified Map
of name value pairs.
Typical usage might look like the following:
public class RepositoryDemo<S extends Session> { private SessionRepository<S> repository; public void demo() { S toSave = this.repository.createSession(); User rwinch = new User("rwinch"); toSave.setAttribute(ATTR_USER, rwinch); this.repository.save(toSave); S session = this.repository.findById(toSave.getId()); User user = session.getAttribute(ATTR_USER); assertThat(user).isEqualTo(rwinch); } // ... setter methods ... }
We create a | |
We create a new | |
We interact with the | |
We now save the | |
We retrieve the | |
We obtain the persisted |
Session
API also provides attributes related to the Session
instance’s expiration.
Typical usage might look like the following:
public class ExpiringRepositoryDemo<S extends Session> { private SessionRepository<S> repository; public void demo() { S toSave = this.repository.createSession(); // ... toSave.setMaxInactiveInterval(Duration.ofSeconds(30)); this.repository.save(toSave); S session = this.repository.findById(toSave.getId()); // ... } // ... setter methods ... }
We create a | |
We create a new | |
We interact with the | |
We now save the | |
We retrieve the |
A SessionRepository
is in charge of creating, retrieving, and persisting Session
instances.
If possible, developers should not interact directly with a SessionRepository
or a Session
.
Instead, developers should prefer interacting with SessionRepository
and Session
indirectly through the HttpSession and WebSocket integration.
Spring Session’s most basic API for using a Session
is the SessionRepository
.
This API is intentionally very simple, so that it is easy to provide additional implementations with basic functionality.
Some SessionRepository
implementations may choose to implement FindByIndexNameSessionRepository
also.
For example, Spring’s Redis, JDBC and Hazelcast support all implement FindByIndexNameSessionRepository
.
The FindByIndexNameSessionRepository
provides a method to look up all the sessions with a given index name and index value.
As a common use case that is supported by all provided FindByIndexNameSessionRepository
implementations, there’s a convenient method to look up all the sessions for a particular user.
This is done by ensuring that the session attribute with the name FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
is populated with the username.
It is the responsibility of the developer to ensure the attribute is populated since Spring Session is not aware of the authentication mechanism being used.
An example of how this might be used can be seen below:
String username = "username"; this.session.setAttribute( FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
Note | |
---|---|
Some implementations of |
Once the session is indexed, it can be found using the following:
String username = "username"; Map<String, Session> sessionIdToSession = this.sessionRepository .findByPrincipalName(username);
A ReactiveSessionRepository
is in charge of creating, retrieving, and persisting Session
instances in a non-blocking and reactive manner.
If possible, developers should not interact directly with a ReactiveSessionRepository
or a Session
.
Instead, developers should prefer interacting with ReactiveSessionRepository
and Session
indirectly through the WebSession integration.
The @EnableSpringHttpSession
annotation can be added to an @Configuration
class to expose the SessionRepositoryFilter
as a bean named "springSessionRepositoryFilter".
In order to leverage the annotation, a single SessionRepository
bean must be provided.
For example:
@EnableSpringHttpSession @Configuration public class SpringHttpSessionConfig { @Bean public MapSessionRepository sessionRepository() { return new MapSessionRepository(new ConcurrentHashMap<>()); } }
It is important to note that no infrastructure for session expirations is configured for you out of the box. This is because things like session expiration are highly implementation dependent. This means if you require cleaning up expired sessions, you are responsible for cleaning up the expired sessions.
The @EnableSpringWebSession
annotation can be added to an @Configuration
class to expose the WebSessionManager
as a bean named "webSessionManager".
In order to leverage the annotation, a single ReactiveSessionRepository
bean must be provided.
For example:
@EnableSpringWebSession public class SpringWebSessionConfig { @Bean public ReactiveSessionRepository reactiveSessionRepository() { return new ReactiveMapSessionRepository(new ConcurrentHashMap<>()); } }
It is important to note that no infrastructure for session expirations is configured for you out of the box. This is because things like session expiration are highly implementation dependent. This means if you require cleaning up expired sessions, you are responsible for cleaning up the expired sessions.
RedisOperationsSessionRepository
is a SessionRepository
that is implemented using Spring Data’s RedisOperations
.
In a web environment, this is typically used in combination with SessionRepositoryFilter
.
The implementation supports SessionDestroyedEvent
and SessionCreatedEvent
through SessionMessageListener
.
A typical example of how to create a new instance can be seen below:
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); // ... configure redisTemplate ... SessionRepository<? extends Session> repository = new RedisOperationsSessionRepository(redisTemplate);
For additional information on how to create a RedisConnectionFactory
, refer to the Spring Data Redis Reference.
In a web environment, the simplest way to create a new RedisOperationsSessionRepository
is to use @EnableRedisHttpSession
.
Complete example usage can be found in the Chapter 3, Samples and Guides (Start Here)
You can use the following attributes to customize the configuration:
<redisNamespace>:
.
save
is invoked on SessionRepository
.
A value of RedisFlushMode.IMMEDIATE
will write to Redis as soon as possible.
RedisOperationsSessionRepository
is subscribed to receive events from redis using a RedisMessageListenerContainer
.
You can customize the way those events are dispatched, by creating a Bean named springSessionRedisTaskExecutor
and/or a Bean springSessionRedisSubscriptionExecutor
.
More details on configuring redis task executors can be found here.
The sections below outline how Redis is updated for each operation. An example of creating a new session can be found below. The subsequent sections describe the details.
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \ maxInactiveInterval 1800 \ lastAccessedTime 1404360000000 \ sessionAttr:attrName someAttrValue \ sessionAttr2:attrName 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
Each session is stored in Redis as a Hash. Each session is set and updated using the HMSET command. An example of how each session is stored can be seen below.
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \ maxInactiveInterval 1800 \ lastAccessedTime 1404360000000 \ sessionAttr:attrName someAttrValue \ sessionAttr2:attrName someAttrValue2
In this example, the session following statements are true about the session:
The Session
instances managed by RedisOperationsSessionRepository
keeps track of the properties that have changed and only updates those.
This means if an attribute is written once and read many times we only need to write that attribute once.
For example, assume the session attribute "sessionAttr2" from earlier was updated.
The following would be executed upon saving:
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
An expiration is associated to each session using the EXPIRE command based upon the Session.getMaxInactiveInterval()
.
For example:
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
You will note that the expiration that is set is 5 minutes after the session actually expires. This is necessary so that the value of the session can be accessed when the session expires. An expiration is set on the session itself five minutes after it actually expires to ensure it is cleaned up, but only after we perform any necessary processing.
Note | |
---|---|
The |
Spring Session relies on the delete and expired keyspace notifications from Redis to fire a SessionDeletedEvent and SessionExpiredEvent respectively.
It is the SessionDeletedEvent
or SessionExpiredEvent
that ensures resources associated with the Session are cleaned up.
For example, when using Spring Session’s WebSocket support the Redis expired or delete event is what triggers any WebSocket connections associated with the session to be closed.
Expiration is not tracked directly on the session key itself since this would mean the session data would no longer be available. Instead a special session expires key is used. In our example the expires key is:
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe "" EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
When a session expires key is deleted or expires, the keyspace notification triggers a lookup of the actual session and a SessionDestroyedEvent is fired.
One problem with relying on Redis expiration exclusively is that Redis makes no guarantee of when the expired event will be fired if the key has not been accessed. Specifically the background task that Redis uses to clean up expired keys is a low priority task and may not trigger the key expiration. For additional details see Timing of expired events section in the Redis documentation.
To circumvent the fact that expired events are not guaranteed to happen we can ensure that each key is accessed when it is expected to expire. This means that if the TTL is expired on the key, Redis will remove the key and fire the expired event when we try to access the key.
For this reason, each session expiration is also tracked to the nearest minute. This allows a background task to access the potentially expired sessions to ensure that Redis expired events are fired in a more deterministic fashion. For example:
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe EXPIRE spring:session:expirations1439245080000 2100
The background task will then use these mappings to explicitly request each key. By accessing the key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
Note | |
---|---|
We do not explicitly delete the keys since in some instances there may be a race condition that incorrectly identifies a key as expired when it is not. Short of using distributed locks (which would kill our performance) there is no way to ensure the consistency of the expiration mapping. By simply accessing the key, we ensure that the key is only removed if the TTL on that key is expired. |
SessionDeletedEvent
and SessionExpiredEvent
are both types of SessionDestroyedEvent
.
RedisOperationsSessionRepository
supports firing a SessionDeletedEvent
whenever a Session
is deleted or a SessionExpiredEvent
when it expires.
This is necessary to ensure resources associated with the Session
are properly cleaned up.
For example, when integrating with WebSockets the SessionDestroyedEvent
is in charge of closing any active WebSocket connections.
Firing SessionDeletedEvent
or SessionExpiredEvent
is made available through the SessionMessageListener
which listens to Redis Keyspace events.
In order for this to work, Redis Keyspace events for Generic commands and Expired events needs to be enabled.
For example:
redis-cli config set notify-keyspace-events Egx
If you are using @EnableRedisHttpSession
the SessionMessageListener
and enabling the necessary Redis Keyspace events is done automatically.
However, in a secured Redis enviornment the config command is disabled.
This means that Spring Session cannot configure Redis Keyspace events for you.
To disable the automatic configuration add ConfigureRedisAction.NO_OP
as a bean.
For example, Java Configuration can use the following:
@Bean public static ConfigureRedisAction configureRedisAction() { return ConfigureRedisAction.NO_OP; }
XML Configuration can use the following:
<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
When a session is created an event is sent to Redis with the channel of spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe
such that 33fdd1b6-b496-4b33-9f7d-df96679d32fe
is the session ID. The body of the event will be the session that was created.
If registered as a MessageListener (default), then RedisOperationsSessionRepository
will then translate the Redis message into a SessionCreatedEvent
.
After installing redis-cli, you can inspect the values in Redis using the redis-cli. For example, enter the following into a terminal:
$ redis-cli redis 127.0.0.1:6379> keys * 1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" 2) "spring:session:expirations:1418772300000"
The suffix of this key is the session identifier of the Spring Session. | |
This key contains all the session IDs that should be deleted at the time |
You can also view the attributes of each session.
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"
ReactiveRedisOperationsSessionRepository
is a ReactiveSessionRepository
that is implemented using Spring Data’s ReactiveRedisOperations
.
In a web environment, this is typically used in combination with WebSessionStore
.
A typical example of how to create a new instance can be seen below:
// ... create and configure connectionFactory and serializationContext ... ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>( connectionFactory, serializationContext); ReactiveSessionRepository<? extends Session> repository = new ReactiveRedisOperationsSessionRepository(redisTemplate);
For additional information on how to create a ReactiveRedisConnectionFactory
, refer to the Spring Data Redis Reference.
In a web environment, the simplest way to create a new ReactiveRedisOperationsSessionRepository
is to use @EnableRedisWebSession
.
You can use the following attributes to customize the configuration:
<redisNamespace>:
.
save
is invoked on ReactiveSessionRepository
.
A value of RedisFlushMode.IMMEDIATE
will write to Redis as soon as possible.
After installing redis-cli, you can inspect the values in Redis using the redis-cli. For example, enter the following into a terminal:
$ redis-cli redis 127.0.0.1:6379> keys * 1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021"
You can also view the attributes of each session.
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"
The MapSessionRepository
allows for persisting Session
in a Map
with the key being the Session
ID and the value being the Session
.
The implementation can be used with a ConcurrentHashMap
as a testing or convenience mechanism.
Alternatively, it can be used with distributed Map
implementations. For example, it can be used with Hazelcast.
Creating a new instance is as simple as:
SessionRepository<? extends Session> repository = new MapSessionRepository( new ConcurrentHashMap<>());
The Hazelcast Sample is a complete application demonstrating using Spring Session with Hazelcast.
To run it use the following:
./gradlew :samples:hazelcast:tomcatRun
The Hazelcast Spring Sample is a complete application demonstrating using Spring Session with Hazelcast and Spring Security.
It includes example Hazelcast MapListener
implementations that support firing SessionCreatedEvent
, SessionDeletedEvent
and SessionExpiredEvent
.
To run it use the following:
./gradlew :samples:hazelcast-spring:tomcatRun
The ReactiveMapSessionRepository
allows for persisting Session
in a Map
with the key being the Session
ID and the value being the Session
.
The implementation can be used with a ConcurrentHashMap
as a testing or convenience mechanism.
Alternatively, it can be used with distributed Map
implementations with the requirement that the supplied Map
must be a non-blocking.
JdbcOperationsSessionRepository
is a SessionRepository
implementation that uses Spring’s JdbcOperations
to store sessions in a relational database.
In a web environment, this is typically used in combination with SessionRepositoryFilter
.
Please note that this implementation does not support publishing of session events.
A typical example of how to create a new instance can be seen below:
JdbcTemplate jdbcTemplate = new JdbcTemplate(); // ... configure JdbcTemplate ... PlatformTransactionManager transactionManager = new DataSourceTransactionManager(); // ... configure transactionManager ... SessionRepository<? extends Session> repository = new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
For additional information on how to create and configure JdbcTemplate
and PlatformTransactionManager
, refer to the Spring Framework Reference Documentation.
In a web environment, the simplest way to create a new JdbcOperationsSessionRepository
is to use @EnableJdbcHttpSession
.
Complete example usage can be found in the Chapter 3, Samples and Guides (Start Here)
You can use the following attributes to customize the configuration:
You can customize the BLOB handling by creating a Bean named springSessionLobHandler
that implements LobHandler
.
You can customize the default serialization and deserialization of the session by providing a ConversionService
instance.
When working in a typical Spring environment, the default ConversionService
Bean (named conversionService
) will be automatically picked up and used for serialization and deserialization.
However, you can override the default ConversionService
by providing a Bean named springSessionConversionService
.
By default, this implementation uses SPRING_SESSION
and SPRING_SESSION_ATTRIBUTES
tables to store sessions.
Note that the table name can be easily customized as already described. In that case the table used to store attributes will be named using the provided table name, suffixed with _ATTRIBUTES
.
If further customizations are needed, SQL queries used by the repository can be customized using set*Query
setter methods. In this case you need to manually configure the sessionRepository
bean.
Due to the differences between the various database vendors, especially when it comes to storing binary data, make sure to use SQL script specific to your database.
Scripts for most major database vendors are packaged as org/springframework/session/jdbc/schema-*.sql
, where *
is the target database type.
For example, with PostgreSQL database you would use the following schema script:
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 );
And with MySQL database:
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;
All JDBC operations in JdbcOperationsSessionRepository
are executed in a transactional manner.
Transactions are executed with propagation set to REQUIRES_NEW
in order to avoid unexpected behavior due to interference with existing transactions (for example, executing save
operation in a thread that already participates in a read-only transaction).
HazelcastSessionRepository
is a SessionRepository
implementation that stores sessions in Hazelcast’s distributed IMap
.
In a web environment, this is typically used in combination with SessionRepositoryFilter
.
A typical example of how to create a new instance can be seen below:
Config config = new Config(); // ... configure Hazelcast ... HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config); HazelcastSessionRepository repository = new HazelcastSessionRepository(hazelcastInstance);
For additional information on how to create and configure Hazelcast instance, refer to the Hazelcast documentation.
If you wish to use Hazelcast as your backing source for the SessionRepository
, then the @EnableHazelcastHttpSession
annotation
can be added to an @Configuration
class. This extends the functionality provided by the @EnableSpringHttpSession
annotation but makes the SessionRepository
for you in Hazelcast.
You must provide a single HazelcastInstance
bean for the configuration to work.
Complete configuration example can be found in the Chapter 3, Samples and Guides (Start Here)
You can use the following attributes on @EnableHazelcastHttpSession
to customize the configuration:
Map
that will be used in Hazelcast to store the session data.
Using a MapListener
to respond to entries being added, evicted, and removed from the distributed Map
, these events will trigger
publishing SessionCreatedEvent
, SessionExpiredEvent
, and SessionDeletedEvent
events respectively using the ApplicationEventPublisher
.
Sessions will be stored in a distributed IMap
in Hazelcast.
The IMap
interface methods will be used to get()
and put()
Sessions.
Additionally, values()
method is used to support FindByIndexNameSessionRepository#findByIndexNameAndIndexValue
operation, together with appropriate ValueExtractor
that needs to be registered with Hazelcast. Refer to Hazelcast Spring Sample for more details on this configuration.
The expiration of a session in the IMap
is handled by Hazelcast’s support for setting the time to live on an entry when it is put()
into the IMap
. Entries (sessions) that have been idle longer than the time to live will be automatically removed from the IMap
.
You shouldn’t need to configure any settings such as max-idle-seconds
or time-to-live-seconds
for the IMap
within the Hazelcast configuration.
Note that if you use Hazelcast’s MapStore
to persist your sessions IMap
there are some limitations when reloading the sessions from MapStore
:
EntryAddedListener
which results in SessionCreatedEvent
being re-published
IMap
which results in sessions losing their original TTL