From 484745887942b60aac75e9bedf63fc0a84d1aaf1 Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Mon, 22 May 2023 16:20:18 -0300 Subject: [PATCH] Add Using Redis Closes gh-2303 --- gradle.properties | 1 + .../ROOT/assets/images/inmemory-sessions.odg | Bin 19648 -> 19647 bytes .../assets/images/shared-session-storage.odg | Bin 20326 -> 20325 bytes .../pages/getting-started/using-redis.adoc | 419 ++++++++++++++++++ .../modules/ROOT/pages/guides/boot-redis.adoc | 15 +- .../spring-session-docs.gradle | 6 +- .../java/sample/config/SessionConfig.java | 2 + 7 files changed, 436 insertions(+), 7 deletions(-) create mode 100644 spring-session-docs/modules/ROOT/pages/getting-started/using-redis.adoc diff --git a/gradle.properties b/gradle.properties index 0f4b03c4..e508c43e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.parallel=true version=3.0.2-SNAPSHOT +springBootVersion=3.0.7 diff --git a/spring-session-docs/modules/ROOT/assets/images/inmemory-sessions.odg b/spring-session-docs/modules/ROOT/assets/images/inmemory-sessions.odg index 30db33310fe27007dce135ad3ccab30abe6d7916..6d5b82bac3c8052abd4625069617c2b1148d13cd 100644 GIT binary patch delta 14 VcmX>wlX3q{#tr!@n+sF|GypJb1-1YH delta 16 Xcmdl#lkvbz#tr!@jJ%ukRRS~sIHv`} diff --git a/spring-session-docs/modules/ROOT/assets/images/shared-session-storage.odg b/spring-session-docs/modules/ROOT/assets/images/shared-session-storage.odg index 2af120cc3dc33e4ff36d5a2b6c2205fd2fe3e33a..c57acda76eb37793018cc0cda8035511fd198dfb 100644 GIT binary patch delta 14 VcmaDhkMZd|#tptIoBdQIv;i`)1(N^( delta 16 XcmaDlkMY?&#tptIjJ%tDRV1_lJ5>ds diff --git a/spring-session-docs/modules/ROOT/pages/getting-started/using-redis.adoc b/spring-session-docs/modules/ROOT/pages/getting-started/using-redis.adoc new file mode 100644 index 00000000..8b36ff68 --- /dev/null +++ b/spring-session-docs/modules/ROOT/pages/getting-started/using-redis.adoc @@ -0,0 +1,419 @@ +[[using-redis]] += Using Spring Session with Redis + +Spring Session uses https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[Spring Data Redis] to support managing the session information in Redis. +In order to configure your application, you must choose what type of application you have: + +- <> +- <> + +[[spring-boot-configuration]] +== Spring Boot Configuration + +=== Adding the Dependencies + +First, you need to add the `spring-session-data-redis` dependency: + +==== +.pom.xml +[source,xml,role="primary"] +[subs="verbatim,attributes"] +---- + + + org.springframework.session + spring-session-data-redis + + +---- + +.build.gradle +[source,groovy,role="secondary"] +---- +implementation("org.springframework.session:spring-session-data-redis") +---- +==== + +As <>, we also need to add the Spring Data Redis dependency to our application, for that we can use the `spring-boot-starter-data-redis` dependency: + +==== +.pom.xml +[source,xml,role="primary"] +[subs="verbatim,attributes"] +---- + + + org.springframework.boot + spring-boot-starter-data-redis + + +---- + +.build.gradle +[source,groovy,role="secondary"] +---- +implementation("org.springframework.boot:spring-boot-starter-data-redis") +---- +==== + +Since we are using Spring Boot, it already {spring-boot-ref-docs}/web.html#web.spring-session[provides auto-configuration for the Redis support]. + +[NOTE] +==== +You can take control over Spring Session’s configuration using `@Enable*HttpSession` (servlet) or `@Enable*WebSession` (reactive). +This will cause the auto-configuration to back off. +Spring Session can then be configured using the annotation’s attributes rather than the configuration properties. +==== + +The basic setup is done, your application should be using Spring Session backed by Redis. +If you need, you can refer to {gh-samples-url}spring-session-sample-boot-redis[a sample Spring Boot application with Spring Session backed by Redis]. + +[[java-configuration]] +== Spring Java Configuration + +=== Adding the Dependencies + +First, we need to add the `spring-session-data-redis` and the `lettuce-core` dependency: + +==== +.pom.xml +[source,xml,role="primary"] +[subs="verbatim,attributes"] +---- + + + org.springframework.session + spring-session-data-redis + {spring-session-version} + + + io.lettuce + lettuce-core + {lettuce-core-version} + + +---- + +.build.gradle +[source,groovy,role="secondary"] +---- +implementation("org.springframework.session:spring-session-data-redis:{spring-session-version}") +implementation("io.lettuce:lettuce-core:{lettuce-core-version}") +---- +==== + +[[creating-spring-configuration]] +=== Creating the Spring Configuration + +After adding the required dependencies, we can create our Spring configuration. +The Spring configuration is responsible for creating a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session. +To do so, add the following Spring Configuration: + +==== +[source,java] +---- +include::{samples-dir}spring-session-sample-javaconfig-redis/src/main/java/sample/Config.java[tags=class] +---- +==== + +<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `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 Redis. +<2> We create a `RedisConnectionFactory` that connects Spring Session to the Redis Server using the `LettuceConnectionFactory`. +By default, it connects to `localhost` on the default port (6379). +For more information on configuring Spring Data Redis, see the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/#redis:connectors[reference documentation]. + +=== Initializing the Configuration into the Java Servlet Container + +Our <> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`. +The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session. + +In order for our `Filter` to work, Spring needs to load our `Config` class. +Last, we need to ensure that our Servlet Container uses our `springSessionRepositoryFilter` for every request. +Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` to help with both of these steps. +The following shows an example: + +==== +.src/main/java/sample/Initializer.java +[source,java] +---- +include::{samples-dir}spring-session-sample-javaconfig-redis/src/main/java/sample/Initializer.java[tags=class] +---- +==== + +NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`. + +<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`. +Doing so ensures that the Spring Bean by the name of `springSessionRepositoryFilter` is registered with our Servlet Container for every request. +<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to ensure Spring loads our `Config`. + +== Further Customizations + +Now that you have your application configured, you might want to start customizing things: + +- I want to {spring-boot-ref-docs}/application-properties.html#application-properties.data.spring.data.redis.host[customize the Redis configuration] using Spring Boot properties +- I want to customize the Redis configuration by <>. +- I want <> `RedisSessionRepository` or `RedisIndexedSessionRepository`. +- I want to <>. +- I want to <>. +- I want to <>. +- I want to <> + +[[serializing-session-using-json]] +=== Serializing the Session using JSON + +By default, Spring Session uses Java Serialization to serialize the session attributes. +You can provide a `RedisSerializer` bean to customize how the session is serialized into Redis. +Spring Data Redis provides the `GenericJackson2JsonRedisSerializer` that serializes and deserializes objects using Jackson's `ObjectMapper`. + +==== +.Configuring the RedisSerializer +[source,java] +---- +include::{samples-dir}spring-session-sample-boot-redis-json/src/main/java/sample/config/SessionConfig.java[tags=class] +---- +==== + +The above code snippet is using Spring Security, therefore we are creating a custom `ObjectMapper` that uses Spring Security's Jackson modules. +If you do not need Spring Security Jackson modules, you can inject your application's `ObjectMapper` bean and use it like so: + +==== +[source,java] +---- +@Bean +public RedisSerializer springSessionDefaultRedisSerializer(ObjectMapper objectMapper) { + return new GenericJackson2JsonRedisSerializer(objectMapper); +} +---- +==== + +[[using-a-different-namespace]] +=== Specifying a Different Namespace + +It is not uncommon to have multiple applications that use the same Redis instance. +For that reason, Spring Session uses a `namespace` (defaults to `spring:session`) to keep the session data separated if needed. + +==== Using Spring Boot Properties + +You can specify it by setting the `spring.session.redis.namespace` property. + +==== +.application.properties +[source,properties,role="primary"] +---- +spring.session.redis.namespace=spring:session:myapplication +---- + +.application.yml +[source,yml,role="secondary"] +---- +spring: + session: + redis: + namespace: "spring:session:myapplication" +---- +==== + +==== Using the Annotation's Attributes + +You can specify the `namespace` by setting the `redisNamespace` property in the `@EnableRedisHttpSession`, `@EnableRedisIndexedHttpSession`, or `@EnableRedisWebSession` annotations: + +==== +.@EnableRedisHttpSession +[source,java,role="primary"] +---- +@Configuration +@EnableRedisHttpSession(redisNamespace = "spring:session:myapplication") +public class SessionConfig { + // ... +} +---- + +.@EnableRedisIndexedHttpSession +[source,java,role="secondary"] +---- +@Configuration +@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:myapplication") +public class SessionConfig { + // ... +} +---- + +.@EnableRedisWebSession +[source,java,role="secondary"] +---- +@Configuration +@EnableRedisWebSession(redisNamespace = "spring:session:myapplication") +public class SessionConfig { + // ... +} +---- +==== + +[[choosing-between-regular-and-indexed]] +=== Choosing Between `RedisSessionRepository` and `RedisIndexedSessionRepository` + +When working with Spring Session Redis, you will likely have to choose between the `RedisSessionRepository` and the `RedisIndexedSessionRepository`. +Both are implementations of the `SessionRepository` interface that store session data in Redis. +However, they differ in how they handle session indexing and querying. + +- `RedisSessionRepository`: `RedisSessionRepository` is a basic implementation that stores session data in Redis without any additional indexing. +It uses a simple key-value structure to store session attributes. +Each session is assigned a unique session ID, and the session data is stored under a Redis key associated with that ID. +When a session needs to be retrieved, the repository queries Redis using the session ID to fetch the associated session data. +Since there is no indexing, querying sessions based on attributes or criteria other than the session ID can be inefficient. + +- `RedisIndexedSessionRepository`: `RedisIndexedSessionRepository` is an extended implementation that provides indexing capabilities for sessions stored in Redis. +It introduces additional data structures in Redis to efficiently query sessions based on attributes or criteria. +In addition to the key-value structure used by `RedisSessionRepository`, it maintains additional indexes to enable fast lookups. +For example, it may create indexes based on session attributes like user ID or last access time. +These indexes allow for efficient querying of sessions based on specific criteria, enhancing performance and enabling advanced session management features. +In addition to that, `RedisIndexedSessionRepository` also supports session expiration and deletion. + +==== Configuring the `RedisSessionRepository` + +===== Using Spring Boot Properties + +If you are using Spring Boot, the `RedisSessionRepository` is the default implementation. +However, if you want to be explicit about it, you can set the following property in your application: + +==== +.application.properties +[source,properties,role="primary"] +---- +spring.session.redis.repository-type=default +---- + +.application.yml +[source,yml,role="secondary"] +---- +spring: + session: + redis: + repository-type: default +---- +==== + +===== Using Annotations + +You can configure the `RedisSessionRepository` by using the `@EnableRedisHttpSession` annotation: + +==== +[source,java,role="primary"] +---- +@Configuration +@EnableRedisHttpSession +public class SessionConfig { + // ... +} +---- +==== + +[[configuring-redisindexedsessionrepository]] +==== Configuring the `RedisIndexedSessionRepository` + +===== Using Spring Boot Properties + +You can configure the `RedisIndexedSessionRepository` by setting the following properties in your application: + +==== +.application.properties +[source,properties,role="primary"] +---- +spring.session.redis.repository-type=indexed +---- + +.application.yml +[source,yml,role="secondary"] +---- +spring: + session: + redis: + repository-type: indexed +---- +==== + +===== Using Annotations + +You can configure the `RedisIndexedSessionRepository` by using the `@EnableRedisIndexedHttpSession` annotation: + +==== +[source,java,role="primary"] +---- +@Configuration +@EnableRedisIndexedHttpSession +public class SessionConfig { + // ... +} +---- +==== + +[[listening-session-events]] +=== Listening to Session Events + +Often times it is valuable to react to session events, for example, you might want to do some kind of processing depending on the session lifecycle. +In order to be able to do that, you must be using the <>. +If you do not know the difference between the indexed and the default repository, you can go to <>. + +With the indexed repository configured, you can now start to listen to `SessionCreatedEvent`, `SessionDeletedEvent`, `SessionDestroyedEvent` and `SessionExpiredEvent` events. +There are a https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events[few ways to listen to application events] in Spring, we are going to use the `@EventListener` annotation. + +==== +[source,java] +---- +@Component +public class SessionEventListener { + + @EventListener + public void processSessionCreatedEvent(SessionCreatedEvent event) { + // do the necessary work + } + + @EventListener + public void processSessionDeletedEvent(SessionDeletedEvent event) { + // do the necessary work + } + + @EventListener + public void processSessionDestroyedEvent(SessionDestroyedEvent event) { + // do the necessary work + } + + @EventListener + public void processSessionExpiredEvent(SessionExpiredEvent event) { + // do the necessary work + } + +} +---- +==== + +[[finding-all-user-sessions]] +=== Finding All Sessions of a Specific User + +By retrieving all sessions of a specific user, you can track the user's active sessions across devices or browsers. +For example, you can use this information session management purposes, such as allowing the user to invalidate or logout from specific sessions or performing actions based on the user's session activity. + +To do that, first you must be using the <>, and then you can inject the `FindByIndexNameSessionRepository` interface, like so: + +==== +[source,java] +---- +@Autowired +public FindByIndexNameSessionRepository sessions; + +public Collection getSessions(Principal principal) { + Collection usersSessions = this.sessions.findByPrincipalName(principal.getName()).values(); + return usersSessions; +} + +public void removeSession(Principal principal, String sessionIdToDelete) { + Set usersSessionIds = this.sessions.findByPrincipalName(principal.getName()).keySet(); + if (usersSessionIds.contains(sessionIdToDelete)) { + this.sessions.deleteById(sessionIdToDelete); + } +} +---- +==== + +In the example above, you can use the `getSessions` method to find all sessions of a specific user, and the `removeSession` method to remove a specific session of a user. diff --git a/spring-session-docs/modules/ROOT/pages/guides/boot-redis.adoc b/spring-session-docs/modules/ROOT/pages/guides/boot-redis.adoc index 4f4f53f7..a2d0297f 100644 --- a/spring-session-docs/modules/ROOT/pages/guides/boot-redis.adoc +++ b/spring-session-docs/modules/ROOT/pages/guides/boot-redis.adoc @@ -13,13 +13,12 @@ link:../index.html[Index] == Updating Dependencies -Before you use Spring Session, you must ensure your dependencies. +Before you use Spring Session with Redis, you must ensure that you have the right dependencies. We assume you are working with a working Spring Boot web application. -If you are using Maven, you must add the following dependencies: ==== .pom.xml -[source,xml] +[source,xml,role="primary"] [subs="verbatim,attributes"] ---- @@ -31,6 +30,12 @@ If you are using Maven, you must add the following dependencies: ---- + +.build.gradle +[source,groovy,role="secondary"] +---- +implementation("org.springframework.session:spring-session-data-redis") +---- ==== Spring Boot provides dependency management for Spring Session modules, so you need not explicitly declare dependency version. @@ -39,7 +44,7 @@ Spring Boot provides dependency management for Spring Session modules, so you ne == Spring Boot Configuration After adding the required dependencies, we can create our Spring Boot configuration. -Thanks to first-class auto configuration support, setting up Spring Session backed by Redis is as simple as adding a single configuration property to your `application.properties`, as the following listing shows: +Thanks to first-class auto-configuration support, setting up Spring Session backed by Redis is as simple as adding a single configuration property to your `application.properties`, as the following listing shows: ==== .src/main/resources/application.properties @@ -124,7 +129,7 @@ Now you can try using the application. Enter the following to log in: * *Password* _password_ Now click the *Login* button. -You should now see a message indicating your are logged in with the user entered previously. +You should now see a message indicating you are logged in with the user entered previously. The user's information is stored in Redis rather than Tomcat's `HttpSession` implementation. [[boot-how]] diff --git a/spring-session-docs/spring-session-docs.gradle b/spring-session-docs/spring-session-docs.gradle index 9a807723..51a78f7b 100644 --- a/spring-session-docs/spring-session-docs.gradle +++ b/spring-session-docs/spring-session-docs.gradle @@ -51,7 +51,7 @@ def generateAttributes() { def snapshotBuild = project.version.contains("SNAPSHOT") def milestoneBuild = project.version.contains("-M") def releaseBuild = (!snapshotBuild && !milestoneBuild) - def springBootVersion = "2.7.0" + def springBootVersion = project.springBootVersion def downloadUrl = "https://github.com/spring-projects/spring-session/archive/${ghTag}.zip" def ghSamplesUrl = "$ghUrl/spring-session-samples/" def samplesDir = "example${dollar}spring-session-samples/" @@ -61,6 +61,7 @@ def generateAttributes() { def websocketdocTestDir = "example${dollar.toString()}java/docs/websocket/" def docsTestResourcesDir = "example${dollar.toString()}resources/" def indexdocTests = "example${dollar.toString()}java/docs/IndexDocTests.java" + def springBootRefDocs = "https://docs.spring.io/spring-boot/docs/${springBootVersion}/reference/html" return [ 'download-url': downloadUrl.toString(), @@ -76,7 +77,8 @@ def generateAttributes() { 'version-milestone': milestoneBuild, 'version-release': releaseBuild, 'version-snapshot': snapshotBuild, - 'spring-boot-version': springBootVersion + 'spring-boot-version': springBootVersion, + 'spring-boot-ref-docs': springBootRefDocs.toString() ] + resolvedVersions(project.configurations.testRuntimeClasspath) } diff --git a/spring-session-samples/spring-session-sample-boot-redis-json/src/main/java/sample/config/SessionConfig.java b/spring-session-samples/spring-session-sample-boot-redis-json/src/main/java/sample/config/SessionConfig.java index e2a2b965..b1a521bb 100644 --- a/spring-session-samples/spring-session-sample-boot-redis-json/src/main/java/sample/config/SessionConfig.java +++ b/spring-session-samples/spring-session-sample-boot-redis-json/src/main/java/sample/config/SessionConfig.java @@ -28,6 +28,7 @@ import org.springframework.security.jackson2.SecurityJackson2Modules; /** * @author jitendra on 3/3/16. */ +// tag::class[] @Configuration public class SessionConfig implements BeanClassLoaderAware { @@ -60,3 +61,4 @@ public class SessionConfig implements BeanClassLoaderAware { } } +// end::class[]