diff --git a/docs/modules/ROOT/examples/spring/HazelcastHttpSessionConfig.java b/docs/modules/ROOT/examples/spring/HazelcastHttpSessionConfig.java index 0073708a5..72e50bc81 100644 --- a/docs/modules/ROOT/examples/spring/HazelcastHttpSessionConfig.java +++ b/docs/modules/ROOT/examples/spring/HazelcastHttpSessionConfig.java @@ -16,20 +16,16 @@ package docs.http; -import com.hazelcast.config.AttributeConfig; import com.hazelcast.config.Config; import com.hazelcast.config.IndexConfig; import com.hazelcast.config.IndexType; -import com.hazelcast.config.SerializerConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.session.MapSession; import com.hazelcast.spring.session.HazelcastIndexedSessionRepository; -import com.hazelcast.spring.session.HazelcastSessionSerializer; -import com.hazelcast.spring.session.PrincipalNameExtractor; +import com.hazelcast.spring.session.HazelcastSessionConfiguration; import com.hazelcast.spring.session.config.annotation.web.http.EnableHazelcastHttpSession; // tag::config[] @@ -40,16 +36,10 @@ public class HazelcastHttpSessionConfig { @Bean public HazelcastInstance hazelcastInstance() { Config config = new Config(); - AttributeConfig attributeConfig = new AttributeConfig() - .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) - .setExtractorClassName(PrincipalNameExtractor.class.getName()); - config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2> - .addAttributeConfig(attributeConfig) + HazelcastSessionConfiguration.applySerializationConfig(config); // <2> + config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <3> .addIndexConfig( new IndexConfig(IndexType.HASH, HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)); - SerializerConfig serializerConfig = new SerializerConfig(); - serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class); - config.getSerializationConfig().addSerializerConfig(serializerConfig); // <3> return Hazelcast.newHazelcastInstance(config); // <4> } diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 2fc8b8502..766c741bc 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -531,6 +531,7 @@ ** xref:spring:add-caching.adoc[] ** xref:spring:hibernate.adoc[] ** xref:spring:transaction-manager.adoc[] +** xref:spring:hazelcast-spring-session.adoc[] ** xref:spring:spring-session-guide.adoc[] *** xref:spring:spring-hazelcast-support.adoc[] // Feast diff --git a/docs/modules/spring/images/HzSS-Client-Server.png b/docs/modules/spring/images/HzSS-Client-Server.png new file mode 100644 index 000000000..87483837a Binary files /dev/null and b/docs/modules/spring/images/HzSS-Client-Server.png differ diff --git a/docs/modules/spring/images/HzSS-Embedded.png b/docs/modules/spring/images/HzSS-Embedded.png new file mode 100644 index 000000000..82abbf910 Binary files /dev/null and b/docs/modules/spring/images/HzSS-Embedded.png differ diff --git a/docs/modules/spring/pages/hazelcast-spring-session.adoc b/docs/modules/spring/pages/hazelcast-spring-session.adoc new file mode 100644 index 000000000..f52400472 --- /dev/null +++ b/docs/modules/spring/pages/hazelcast-spring-session.adoc @@ -0,0 +1,317 @@ += Configure Hazelcast Spring Session + +Hazelcast Spring Session is a library that allows you to store session information from your Spring Boot application in Hazelcast's xref:data-structures:map.adoc[IMap]. This allows you to enhance your application with the benefits of Hazelcast: resilence, high availability and high performance. + +== Update dependencies + +If you use Maven, add the following dependencies: + +[tabs] +==== +Maven:: ++ +.pom.xml +[source,xml] +[subs="verbatim,attributes"] +---- + + + + com.hazelcast + hazelcast + {os-version} + + + org.springframework + spring-web + 7.0.0 + + + com.hazelcast + hazelcast-spring-session + 4.0.0 + + +---- + +Gradle (Kotlin):: ++ +.build.gradle.kts +[source,kotlin] +[subs="verbatim,attributes"] +---- +dependencies { + implementation("com.hazelcast:hazelcast:{os-version}") + implementation("org.springframework:spring-web:7.0.0") + implementation("com.hazelcast:hazelcast-spring-session:4.0.0") +} +---- + +==== + +TIP: Hazelcast Spring Session uses the same major version number as Spring Boot it was created for. For Spring Boot 4.x, the compatible Hazelcast Spring Session will be always 4.x. + +=== Migrate from Spring Session Hazelcast 3.x + +From version 4.0, the Hazelcast integration with Spring Session is owned by the Hazelcast team. + +To migrate your application from using Spring Session Hazelcast 3.x to the new Hazelcast Spring Session 4.x: + +. Change the GroupId to `com.hazelcast` and artifactId to `hazelcast-spring-session`. +. Update your code and change the packages. All Hazelcast-specific classes were moved from `org.springframework.session.hazelcast` to `com.hazelcast.spring.session`. +. Remove this configuration for `PrincipalNameExtractor`: ++ +[source,java] +---- +AttributeConfig attributeConfig = new AttributeConfig() + .setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) + .setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName()); +config.getMapConfig(SESSIONS_MAP_NAME) + .addAttributeConfig(attributeConfig); +---- +. Change serialization configuration. Replace: ++ +[source,java] +---- +SerializerConfig serializerConfig = new SerializerConfig(); + serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class); + config.getSerializationConfig().addSerializerConfig(serializerConfig); +---- +With a call: ++ +[source,java] +---- +config = HazelcastSessionConfiguration.applySerializationConfig(config); +---- +. Index configuration for `PRINCIPAL_ATTRIBUTE_NAME` **if** no other IMap customization is wanted. + +[[security-spring-configuration]] +== Configure Spring + +Configure Spring to create a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session. Add the following Spring Configuration: + +[source,java] +---- +include::ROOT:example$/spring/HazelcastHttpSessionConfig.java[tags=config] +---- +<1> The `@EnableHazelcastHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that implements `Filter`. +The filter replaces the `HttpSession` implementation to be backed by Spring Session. +In this instance, Spring Session is backed by Hazelcast. +<2> To serialize `BackingMapSession` objects efficiently, `HazelcastSessionCompactSerializer` and `AttributeValueCompactSerializer` needs to be registered. If this is not set, Hazelcast won't be able to deserialize session data. +<3> For best performance when using Hazelcast Spring Session with Spring Security, an index on `HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE` must be added. This step is optional in Hazelcast Spring Session 4.0, as `HazelcastIndexedSessionRepository` will try to add this index automatically. If you want to customize your `MapConfig`, you need to add this index manually as overriding `MapConfig` by `HazelcastIndexedSessionRepository` is not possible. +<4> Create a `HazelcastInstance` that connects Spring Session to Hazelcast. +By default, the application starts and connects to an embedded instance of Hazelcast. + +== Customization options + +To customize the SessionRepository your application will be using, declare a bean of type `SessionRepositoryCustomizer`. For example: + +[source,java] +---- +@Bean +public SessionRepositoryCustomizer customizeSessionRepo() { + return (sessionRepository) -> { + // here you can customize sessionRepository by calling the setter methods, for example: + sessionRepository.setFlushMode(FlushMode.IMMEDIATE); + sessionRepository.setSaveMode(SaveMode.ALWAYS); + sessionRepository.setDeployedOnAllMembers(false); + }; +} +---- + +The following table describes some common customization options: + +[cols="1m,1m,3",options="header"] +|=== +| Method/property | Default value | Description + +| flushMode +| FlushMode.ON_SAVE +| When changes in the session will be persisted. Possible options: + + - `ON_SAVE` - `repository.save()` will persist the changes + - `IMMEDIATE` - changes will be persisted immediately, without the need to call `repository.save()` + +| defaultMaxInactiveInterval +| 30 minutes +| Maximum inactive interval in between requests before the session will be invalidated. A negative time indicates that the session will never time out. + +| sessionMapName +| `spring:session:sessions` +| Name of IMap used to store sessions. + +| saveMode +| `SaveMode.ON_SET_ATTRIBUTE` +| When changes to session attributes will be saved. Possible values: + + - `ON_SET_ATTRIBUTE` - save only changes made to the session. Best if you want to minimize risk of attribute override in highly concurrent environments. + - `ON_GET_ATTRIBUTE` - same as `ON_SET_ATTRIBUTE` plus save is performed also on read attributes. + - `ALWAYS` - always save all session attributes (even when not newly added or read). + +| deployedOnAllMembers +| true +| Setting this property to `true` will perform session change processing server-side (on the Hazelcast members storing the data), which minimizes network traffic. However, it requires the Hazelcast Spring Session JAR to be deployed on all Hazelcast members in the cluster because `SessionUpdateEntryProcessor` will need to be deserialized on cluster members. + +Setting the property to `false` will increase the network traffic and make session change processing slower (more steps involved: lock session key -> get fresh value -> process changes -> set new value -> unlock instead of just one step: send EntryProcessor to cluster), but will allow you to use Hazelcast Spring Session without the need of any server-side (member) changes. + +| indexResolver +| `new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>())` +| IndexResolver to use when querying for `FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME`. + +| sessionIdGenerator +| `UuidSessionIdGenerator.getInstance()` +| How session IDs for new sessions are generated. + +| applicationEventPublisher +| no-op +| Allows setting a custom ApplicationEventPublisher that will be used to publish `AbstractSessionEvent`: `SessionCreatedEvent`, +`SessionExpiredEvent` and `SessionDeletedEvent`. + +| disableSessionMapAutoConfiguration +| - +| If called, the autoconfiguration of IMap storing session data won't be performed and index on `HazelcastIndexedSessionRepository#PRINCIPAL_NAME_ATTRIBUTE` will not be created, potentially harming performance of the application. Can be used if you have pre-existing IMap configuration applied and do not want to convert it to `sessionMapConfigCustomizer`. + +| sessionMapConfigCustomizer +| ConsumerEx.noop() +| Customizes session map configuration (MapConfig). If using client-server architecture, the customized `MapConfig` will not override pre-existing map configuration. + +|=== + +== Supported topologies + +Hazelcast Spring Session supports two topologies. + +=== Client-server + +In this topology, the web application uses xref:clients:client-overview.adoc[Hazelcast clients] to connect to a Hazelcast cluster on a separate JVM. This may be a completely separate machine. xref:maintain-cluster:lite-members.adoc[Lite members] can optionally be used to on the application side. + +image:HzSS-Client-Server.png[Hazelcast Spring Session with Client-Server topology] + +To deploy in a client-server topology: + +* Client side (Spring Boot application): +** Create a `@Bean` of type `ClientConfig` or `HazelcastInstance` that will return the result of `HazelcastClient.newHazelcastClient` method. + +* Server side (Hazelcast member). This configuration is optional, but recommended. +** Deploy the Hazelcast Spring Session JAR onto the classpath with Spring context and Spring Security JARs. +** For best performance, add serializers to your Hazelcast configuration: ++ +[tabs] +==== +XML:: ++ +-- +[source,xml] +---- + + + + com.hazelcast.spring.session.HazelcastSessionCompactSerializer + com.hazelcast.spring.session.AttributeValueCompactSerializer + + + +---- +-- + +YAML:: ++ +-- +[source,yaml] +---- +serialization: + compact-serialization: + serializers: + - serializer: com.hazelcast.spring.session.HazelcastSessionCompactSerializer + - serializer: com.hazelcast.spring.session.AttributeValueCompactSerializer +---- +-- + +Java:: ++ +-- +[source,java] +---- +import com.hazelcast.spring.session.AttributeValueCompactSerializer; +import com.hazelcast.spring.session.HazelcastSessionCompactSerializer; +// ... +config.getSerializationConfig().getCompactSerializationConfig() + .addSerializer(new AttributeValueCompactSerializer()) + .addSerializer(new HazelcastSessionCompactSerializer()); +---- + +Alternatively, you can use the helper method: +[source,java] +---- +import com.hazelcast.spring.session.HazelcastSessionConfiguration; +// ... +HazelcastSessionConfiguration.applySerializationConfig(config); +---- +-- +==== + +=== Embedded + +In this topology, every web application instance has an embedded Hazelcast instance. Data is stored on the same JVMs as the application. + +image:HzSS-Embedded.png[Hazelcast Spring Session with Embedded topology] + +This topology is the easiest to start with as it does not require separate cluster JVMs, however it is less flexible because Hazelcast members scale with application instances. + +To deploy in an embedded topology, you must configure serialization: +[tabs] +==== +Java:: ++ +-- +You can use the helper method (recommended): +[source,java] +---- +import com.hazelcast.spring.session.HazelcastSessionConfiguration; +// ... +HazelcastSessionConfiguration.applySerializationConfig(config); +---- +Alternatively, you can add specific serializers manually: +[source,java] +---- +import com.hazelcast.spring.session.AttributeValueCompactSerializer; +import com.hazelcast.spring.session.HazelcastSessionCompactSerializer; +// ... +config.getSerializationConfig().getCompactSerializationConfig() + .addSerializer(new AttributeValueCompactSerializer()) + .addSerializer(new HazelcastSessionCompactSerializer()); +---- +-- + +XML:: ++ +-- +[source,xml] +---- + + + + com.hazelcast.spring.session.HazelcastSessionCompactSerializer + com.hazelcast.spring.session.AttributeValueCompactSerializer + + + +---- +-- + +YAML:: ++ +-- +[source,yaml] +---- +serialization: + compact-serialization: + serializers: + - serializer: com.hazelcast.spring.session.HazelcastSessionCompactSerializer + - serializer: com.hazelcast.spring.session.AttributeValueCompactSerializer +---- +-- + +==== + +The `deployedOnAllMembers` option in `HazelcastIndexedSessionRepository` must be set to `true` (default), as in embedded mode every mode will always have Hazelcast Spring Session JAR on the classpath. \ No newline at end of file diff --git a/docs/modules/spring/pages/spring-session-guide.adoc b/docs/modules/spring/pages/spring-session-guide.adoc index 4db401f83..f7c2eecb2 100644 --- a/docs/modules/spring/pages/spring-session-guide.adoc +++ b/docs/modules/spring/pages/spring-session-guide.adoc @@ -26,7 +26,7 @@ If you use Maven, you must add the following dependencies: org.springframework spring-web - 6.2.7 + 7.0.0 com.hazelcast @@ -36,15 +36,6 @@ If you use Maven, you must add the following dependencies: ---- -=== Note on migration from Spring Session Hazelcast 3.x - -From version 4.0, the Hazelcast integration with Spring Session is owned by the Hazelcast team. - -To migrate your application from using Spring Session Hazelcast 3.x to new Hazelcast Spring Session 4.x: - -. Change GroupId to `com.hazelcast` and artifactId to `hazelcast-spring-session`. -. Update your code and change the packages. All Hazelcast-specific classes were moved from `org.springframework.session.hazelcast` to `com.hazelcast.spring.session`. - [[security-spring-configuration]] == Spring configuration @@ -59,18 +50,11 @@ include::ROOT:example$/spring/HazelcastHttpSessionConfig.java[tags=config] <1> The `@EnableHazelcastHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that implements `Filter`. The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session. In this instance, Spring Session is backed by Hazelcast. -<2> In order to support retrieval of sessions by principal name index, an appropriate `ValueExtractor` needs to be registered. -Spring Session provides `PrincipalNameExtractor` for this purpose. -<3> In order to serialize `MapSession` objects efficiently, `HazelcastSessionSerializer` needs to be registered. If this -is not set, Hazelcast will serialize sessions using native Java serialization. +<2> To serialize `BackingMapSession` objects efficiently, `HazelcastSessionCompactSerializer` and `AttributeValueCompactSerializer` needs to be registered. If this is not set, Hazelcast won't be able to deserialize session data. +<3> For the best performance when using Hazelcast Spring Session with Spring Security, an index on `HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE` must be added. This step is optional from Hazelcast Spring Session 4.0, as `HazelcastIndexedSessionRepository` will try to add this index automatically. If you want to customize your `MapConfig`, you need to add this index manually as overriding `MapConfig` by `HazelcastIndexedSessionRepository` is not possible. <4> We create a `HazelcastInstance` that connects Spring Session to Hazelcast. By default, the application starts and connects to an embedded instance of Hazelcast. -NOTE: If `HazelcastSessionSerializer` is preferred, it needs to be configured for all Hazelcast cluster members before they start. -In a Hazelcast cluster, all members should use the same serialization method for sessions. Also, if Hazelcast Client/Server topology -is used, then both members and clients must use the same serialization method. The serializer can be registered via `ClientConfig` -with the same `SerializerConfiguration` of members. - == Servlet container initialization The instructions detailed in the link:https://docs.spring.io/spring-session/reference/guides/java-security.html#security-spring-configuration[Spring docs] created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`. diff --git a/pom.xml b/pom.xml index 63a35e5f2..6c673705c 100644 --- a/pom.xml +++ b/pom.xml @@ -63,9 +63,9 @@ 3.12.14 - org.springframework.session - spring-session-hazelcast - 3.5.3 + com.hazelcast + hazelcast-spring-session + 4.0.0-SNAPSHOT