Start Documentation
Issue gh-41
This commit is contained in:
134
docs/src/docs/asciidoc/guides/boot.adoc
Normal file
134
docs/src/docs/asciidoc/guides/boot.adoc
Normal file
@@ -0,0 +1,134 @@
|
||||
= Spring Session - Spring Boot
|
||||
Rob Winch
|
||||
:toc:
|
||||
|
||||
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Spring Boot.
|
||||
|
||||
NOTE: The completed guide can be found in the <<boot-sample, boot sample application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use Spring Session, you must ensure to update your dependencies.
|
||||
We assume you are working with a working Spring Boot web application.
|
||||
If you are using Maven, ensure to add the following dependencies:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom<type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
[[boot-spring-configuration]]
|
||||
== 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.
|
||||
Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}boot/src/main/java/sample/config/HttpSessionConfig.java[]
|
||||
----
|
||||
|
||||
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
The filter is what 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.
|
||||
In our example, we are connecting to localhost on the default port (6379).
|
||||
For more information on configuring Spring Data Redis, refer to the http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
|
||||
|
||||
== Servlet Container Initialization
|
||||
|
||||
Our <<boot-spring-configuration,Spring Configuration>> 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 do its magic, Spring needs to load our `Config` class.
|
||||
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
|
||||
Fortunately, Spring Boot takes care of both of these steps for us.
|
||||
|
||||
[[boot-sample]]
|
||||
== boot Sample Application
|
||||
|
||||
The boot Sample Application demonstrates how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Spring Boot.
|
||||
|
||||
=== Running the boot Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
$ ./gradlew :samples:boot:tomcatRun
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the security Sample Application
|
||||
|
||||
Try using the application. Enter the following to log in:
|
||||
|
||||
* **Username** _user_
|
||||
* **Password** _password_
|
||||
|
||||
Now click the **Login** button.
|
||||
You should now see a message indicating your are logged in with the user entered previously.
|
||||
The user's information is stored in Redis rather than Tomcat's `HttpSession` implementation.
|
||||
|
||||
=== How does it work?
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
|
||||
Spring Session replaces the `HttpSession` with an implementation that is backed by Redis.
|
||||
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Redis.
|
||||
|
||||
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
|
||||
|
||||
$ redis-cli keys '*' | xargs redis-cli del
|
||||
|
||||
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli].
|
||||
|
||||
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
|
||||
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
||||
160
docs/src/docs/asciidoc/guides/httpsession.adoc
Normal file
160
docs/src/docs/asciidoc/guides/httpsession.adoc
Normal file
@@ -0,0 +1,160 @@
|
||||
= Spring Session - HttpSession (Quick Start)
|
||||
Rob Winch
|
||||
:toc:
|
||||
|
||||
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession`.
|
||||
|
||||
NOTE: The completed guide can be found in the <<httpsession-sample, httpsession sample application>>.
|
||||
|
||||
// tag::config[]
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use Spring Session, you must ensure to update your dependencies.
|
||||
If you are using Maven, ensure to add the following dependencies:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom<type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
[[httpsession-spring-configuration]]
|
||||
== 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.
|
||||
Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession/src/main/java/sample/Config.java[]
|
||||
----
|
||||
|
||||
<1> We import an embedded Redis Server so that there is no need to start up Redis external of our application.
|
||||
In a production application this is not necessary since we would point our connection to an external Redis instance.
|
||||
<2> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
||||
In this instance Spring Session is backed by Redis.
|
||||
<3> We create a `RedisConnectionFactory` that connects Spring Session to the Redis Server.
|
||||
In our example, we are connecting to localhost on the default port (6379).
|
||||
For more information on configuring Spring Data Redis, refer to the http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
|
||||
|
||||
== Servlet Container Initialization
|
||||
|
||||
Our <<httpsession-spring-configuration,Spring Configuration>> 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 do its magic, Spring needs to load our `Config` class.
|
||||
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
|
||||
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` both of these steps extremely easy.
|
||||
You can find an example below:
|
||||
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession/src/main/java/sample/Initializer.java[]
|
||||
----
|
||||
|
||||
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`.
|
||||
This ensures that the Spring Bean by the name `springSessionRepositoryFilter` is registered with our Servlet Container for every request.
|
||||
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily ensure Spring loads our `Config`.
|
||||
|
||||
// end::config[]
|
||||
|
||||
[[httpsession-sample]]
|
||||
== httpsession Sample Application
|
||||
|
||||
|
||||
|
||||
=== Running the httpsession Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
$ ./gradlew :samples:httpsession:tomcatRun
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the httpsession Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name** *username*
|
||||
* **Attribute Value** *rob*
|
||||
|
||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
||||
|
||||
=== How does it work?
|
||||
|
||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession/src/main/java/sample/SessionServlet.java[]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
|
||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
|
||||
|
||||
$ redis-cli keys '*' | xargs redis-cli del
|
||||
|
||||
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli].
|
||||
|
||||
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
|
||||
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
||||
162
docs/src/docs/asciidoc/guides/security.adoc
Normal file
162
docs/src/docs/asciidoc/guides/security.adoc
Normal file
@@ -0,0 +1,162 @@
|
||||
= Spring Session and Spring Security
|
||||
Rob Winch
|
||||
:toc:
|
||||
|
||||
This guide describes how to use Spring Session along with Spring Security.
|
||||
It assumes you have already applied Spring Security to your application.
|
||||
|
||||
NOTE: The completed guide can be found in the <<security-sample, security sample application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use Spring Session, you must ensure to update your dependencies.
|
||||
If you are using Maven, ensure to add the following dependencies:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom<type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
[[security-spring-configuration]]
|
||||
== 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.
|
||||
Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}security/src/main/java/sample/Config.java[]
|
||||
----
|
||||
|
||||
<1> We import an embedded Redis Server so that there is no need to start up Redis external of our application.
|
||||
In a production application this is not necessary since we would point our connection to an external Redis instance.
|
||||
<2> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
||||
In this instance Spring Session is backed by Redis.
|
||||
<3> We create a `RedisConnectionFactory` that connects Spring Session to the Redis Server.
|
||||
In our example, we are connecting to localhost on the default port (6379).
|
||||
For more information on configuring Spring Data Redis, refer to the http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
|
||||
|
||||
== Servlet Container Initialization
|
||||
|
||||
Our <<security-spring-configuration,Spring Configuration>> 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 do its magic, Spring needs to load our `Config` class.
|
||||
Since our application is already loading Spring configuration using our `SecurityInitializer` class, we can simply add our Config class to it.
|
||||
|
||||
.src/main/java/sample/SecurityInitializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}security/src/main/java/sample/SecurityInitializer.java[]
|
||||
----
|
||||
|
||||
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
|
||||
It is extremely important that Spring Session's `springSessionRepositoryFilter` is invoked before Spring Security's `springSecurityFilterChain`.
|
||||
This ensures that the `HttpSession` that Spring Security uses is backed by Spring Session.
|
||||
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` that makes this extremely easy.
|
||||
You can find an example below:
|
||||
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}security/src/main/java/sample/Initializer.java[]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
|
||||
By extending `AbstractHttpSessionApplicationInitializer` we ensure that the Spring Bean by the name `springSessionRepositoryFilter` is registered with our Servlet Container for every request before Spring Security's `springSecurityFilterChain` .
|
||||
|
||||
[[security-sample]]
|
||||
== security Sample Application
|
||||
|
||||
|
||||
|
||||
=== Running the security Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
$ ./gradlew :samples:security:tomcatRun
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the security Sample Application
|
||||
|
||||
Try using the application. Enter the following to log in:
|
||||
|
||||
* **Username** _user_
|
||||
* **Password** _password_
|
||||
|
||||
Now click the **Login** button.
|
||||
You should now see a message indicating your are logged in with the user entered previously.
|
||||
The user's information is stored in Redis rather than Tomcat's `HttpSession` implementation.
|
||||
|
||||
=== How does it work?
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
|
||||
Spring Session replaces the `HttpSession` with an implementation that is backed by Redis.
|
||||
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Redis.
|
||||
|
||||
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
|
||||
|
||||
$ redis-cli keys '*' | xargs redis-cli del
|
||||
|
||||
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli].
|
||||
|
||||
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
|
||||
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
||||
153
docs/src/docs/asciidoc/guides/users.adoc
Normal file
153
docs/src/docs/asciidoc/guides/users.adoc
Normal file
@@ -0,0 +1,153 @@
|
||||
= Spring Session - Multiple Sessions
|
||||
Rob Winch
|
||||
:toc:
|
||||
|
||||
This guide describes how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
|
||||
|
||||
== Integrating with Spring Session
|
||||
|
||||
The steps to integrate with Spring Session are exactly the same as those outline in the link:httpsession.html[HttpSession Guide], so we will skip to running the sample application.
|
||||
|
||||
[[users-sample]]
|
||||
== users Sample Application
|
||||
|
||||
The users application demonstrates how to allow an application to manage multiple simultaneous browser sessions (i.e. Google Accounts).
|
||||
|
||||
=== Running the users Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
$ ./gradlew :samples:users:tomcatRun
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the users Sample Application
|
||||
|
||||
Try using the application. Authenticate with the following information:
|
||||
|
||||
* **Username** _rob_
|
||||
* **Password** _rob_
|
||||
|
||||
Now click the **Login** button. You should now be authenticated as the user **rob**.
|
||||
|
||||
We can click on links and our user information is preserved.
|
||||
|
||||
* Click on the **Link** link in the navigation bar at the top
|
||||
* Observe we are still authenticated as **rob**
|
||||
|
||||
Let's add an another account.
|
||||
|
||||
* Return to the *Home* page
|
||||
* Click on the arrow next to *rob* in the upper right hand corner
|
||||
* Click **Add Account**
|
||||
|
||||
The log in form is displayed again. Authenticate with the following information:
|
||||
|
||||
* **Username** _luke_
|
||||
* **Password** _luke_
|
||||
|
||||
Now click the **Login** button. You should now be authenticated as the user **luke**.
|
||||
|
||||
We can click on links and our user information is preserved.
|
||||
|
||||
* Click on the **Link** link in the navigation bar at the top
|
||||
* Observe we are still authenticated as **luke**
|
||||
|
||||
Where did our original user go? Let's switch to our original account.
|
||||
|
||||
* Click on the arrow next to *luke* in the upper right hand corner.
|
||||
* Click on **Switch Account** -> *rob*
|
||||
|
||||
We are now using the session associated with *rob*.
|
||||
|
||||
== How does it work?
|
||||
|
||||
// tag::how-does-it-work[]
|
||||
|
||||
Let's take a look at how Spring Session keeps track of multiple sessions.
|
||||
|
||||
=== Managing a Single Session
|
||||
|
||||
Spring Session keeps track of the `HttpSession` by adding a value to a cookie named SESSION.
|
||||
For example, the SESSION cookie might have a value of:
|
||||
|
||||
7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
=== Adding a Session
|
||||
|
||||
We can add another session by requesting a URL that contains a special parameter in it.
|
||||
By default the parameter name is *_s*. For example, the following URL would create a new session:
|
||||
|
||||
http://localhost:8080/?_s=1
|
||||
|
||||
NOTE: The parameter value does not indicate the actual session id.
|
||||
This is important because we never want to allow the session id to be determined by a client to avoid https://www.owasp.org/index.php/Session_fixation[session fixation attacks].
|
||||
Additionally, we do not want the session id to be leaked since it is sent as a query parameter.
|
||||
Remember sensitive information should only be transmitted as a header or in the body of the request.
|
||||
|
||||
Rather than creating the URL ourselves, we can utilize the `HttpSessionManager` to do this for us.
|
||||
We can obtain the `HttpSessionManager` from the `HttpServletRequest` using the following:
|
||||
|
||||
.src/main/java/sample/UserAccountsFilter.java
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}users/src/main/java/sample/UserAccountsFilter.java[tags=HttpSessionManager]
|
||||
----
|
||||
|
||||
We can now use it to create a URL to add another session.
|
||||
|
||||
.src/main/java/sample/UserAccountsFilter.java
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}users/src/main/java/sample/UserAccountsFilter.java[tags=addAccountUrl]
|
||||
----
|
||||
|
||||
<1> We have an existing variable named `unauthenticatedAlias`.
|
||||
The value is an alias that points to existing unauthenticated session.
|
||||
If no such session exists, the value is null.
|
||||
This ensures if we have an existing unauthenticated session that we use it instead of creating a new session.
|
||||
<2> If all of our sessions are already associated to a user, we create a new session alias.
|
||||
<3> If a there exists a session that is not associated to a user, we use its session alias.
|
||||
<4> Finally, we create the add account URL.
|
||||
The URL contains a session alias that either points to an existing unauthenticated session or is an alias that is unused thus signaling to create a new session associated to that alias.
|
||||
|
||||
Now our SESSION cookie looks something like this:
|
||||
|
||||
0 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 1 1d526d4a-c462-45a4-93d9-84a39b6d44ad
|
||||
|
||||
Such that:
|
||||
|
||||
* There is a session with the id *7e8383a4-082c-4ffe-a4bc-c40fd3363c5e*
|
||||
** The alias for this session is *0*.
|
||||
For example, if the URL is http://localhost:8080/?_s=0 this alias would be used.
|
||||
** This is the default session.
|
||||
This means that if no session alias is specified, then this session is used.
|
||||
For example, if the URL is http://localhost:8080/ this session would be used.
|
||||
* There is a session with the id *1d526d4a-c462-45a4-93d9-84a39b6d44ad*
|
||||
** The alias for this session is *1*.
|
||||
If the session alias is *1*, then this session is used.
|
||||
For example, if the URL is http://localhost:8080/?_s=1 this alias would be used.
|
||||
|
||||
=== Automatic Session Alias Inclusion with encodeURL
|
||||
|
||||
The nice thing about specifying the session alias in the URL is that we can have multiple tabs open with different active sessions.
|
||||
The bad thing is that we need to include the session alias in every URL of our application.
|
||||
Fortunately, Spring Session will automatically include the session alias in any URL that passes through http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html#encodeURL(java.lang.String)[HttpServletResponse#encodeURL(java.lang.String)]
|
||||
|
||||
This means that if you are using standard tag libraries the session alias is automatically included in the URL.
|
||||
For example, if we are currently using the session with the alias of *1*, then the following:
|
||||
|
||||
.src/main/webapp/index.jsp
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}users/src/main/webapp/index.jsp[tags=link]
|
||||
----
|
||||
|
||||
will output a link of:
|
||||
|
||||
[source,html]
|
||||
----
|
||||
<a id="navLink" href="/link.jsp?_s=1">Link</a>
|
||||
----
|
||||
|
||||
end::how-does-it-work[]
|
||||
130
docs/src/docs/asciidoc/guides/websocket.adoc
Normal file
130
docs/src/docs/asciidoc/guides/websocket.adoc
Normal file
@@ -0,0 +1,130 @@
|
||||
= Spring Session - WebSocket
|
||||
Rob Winch
|
||||
:toc:
|
||||
:websocketdoc-test-dir: {docs-test-dir}docs/websocket/
|
||||
|
||||
This guide describes how to use Spring Session to ensure that WebSocket messages keep your HttpSession alive.
|
||||
|
||||
// tag::disclaimer[]
|
||||
|
||||
NOTE: Spring Session's WebSocket support only works with Spring's WebSocket support.
|
||||
Specifically it does not work with using https://www.jcp.org/en/jsr/detail?id=356[JSR-356] directly.
|
||||
This is due to the fact that JSR-356 does not have a mechanism for intercepting incoming WebSocket messages.
|
||||
|
||||
// end::disclaimer[]
|
||||
|
||||
== HttpSession Setup
|
||||
|
||||
The first step is to integrate Spring Session with the HttpSession. These steps are already outlined in the link:httpsession.html[HttpSession Guide].
|
||||
|
||||
Please make sure you have already integrated Spring Session with the HttpSession before proceeding.
|
||||
|
||||
// tag::config[]
|
||||
|
||||
[[websocket-spring-configuration]]
|
||||
== Spring Configuration
|
||||
|
||||
In a typical Spring WebSocket application users would extend `AbstractWebSocketMessageBrokerConfigurer`.
|
||||
For example, the configuration might look something like the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{websocketdoc-test-dir}WebSocketConfig.java[tags=class]
|
||||
----
|
||||
|
||||
To hook in the Spring Session support, we need to ensure
|
||||
|
||||
* `WebSocketConnectHandlerDecoratorFactory` is added as a `WebSocketHandlerDecoratorFactory` to `WebSocketTransportRegistration`.
|
||||
This ensures a custom `SessionConnectEvent` is fired that contains the `WebSocketSession`.
|
||||
The `WebSocketSession` is necessary to terminate any WebSocket connections that are still open when a Spring Session is terminated.
|
||||
* `SessionRepositoryMessageInterceptor` is added as a `HandshakeInterceptor` to every `StompWebSocketEndpointRegistration`.
|
||||
This ensures that the Session is added to the WebSocket properties to enable updating the last accessed time.
|
||||
* `SessionRepositoryMessageInterceptor` is added as a `ChannelInterceptor` to our inbound `ChannelRegistration`.
|
||||
This ensures that every time an inbound message is received, that the last accessed time of our Spring Session is updated.
|
||||
* `WebSocketRegistryListener` is created as a Spring Bean.
|
||||
This ensures that we have a mapping of all of the Session id to the corresponding WebSocket connections.
|
||||
By maintaining this mapping, we can close all the WebSocket connections when a Spring Session (HttpSession) is terminated.
|
||||
|
||||
This is quite a bit of work to get things working.
|
||||
Fortunately, Spring Session provides a convenience class named `AbstractSessionWebSocketMessageBrokerConfigurer` that hides the complexity of the configuration.
|
||||
An example is provided below:
|
||||
|
||||
.src/main/java/samples/config/WebSocketConfig.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}websocket/src/main/java/sample/config/WebSocketConfig.java[tags=class]
|
||||
----
|
||||
|
||||
We only need to change two things to use Spring Session:
|
||||
|
||||
<1> Instead of extending `AbstractWebSocketMessageBrokerConfigurer` we extend `AbstractSessionWebSocketMessageBrokerConfigurer`
|
||||
<2> We rename the `registerStompEndpoints` method to `configureStompEndpoints`
|
||||
|
||||
// end::config[]
|
||||
|
||||
[[websocket-sample]]
|
||||
== websocket Sample Application
|
||||
|
||||
The websocket sample application demonstrates how to use Spring Session with WebSockets.
|
||||
|
||||
=== Running the websocket Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
[TIP]
|
||||
====
|
||||
For the purposes of testing session expiration, you may want to change the session expiration to be 1 minute (default is 30 minutes) by removing the comment from the following file before starting the application:
|
||||
|
||||
.src/main/java/samples/config/WebSecurityConfig.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}websocket/src/main/java/sample/config/WebSecurityConfig.java[tags=enable-redis-httpsession]
|
||||
----
|
||||
====
|
||||
|
||||
$ ./gradlew :samples:websocket:bootRun
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the websocket Sample Application
|
||||
|
||||
Try using the application. Authenticate with the following information:
|
||||
|
||||
* **Username** _rob_
|
||||
* **Password** _password_
|
||||
|
||||
Now click the **Login** button. You should now be authenticated as the user **rob**.
|
||||
|
||||
Open an incognito window and access http://localhost:8080/
|
||||
|
||||
You will be prompted with a log in form. Authenticate with the following information:
|
||||
|
||||
* **Username** _luke_
|
||||
* **Password** _password_
|
||||
|
||||
Now send a message from *rob* to *luke*. The message should appear.
|
||||
|
||||
Wait for two minutes and try sending a message from *rob* to *luke* again.
|
||||
You will see that the message is no longer sent.
|
||||
|
||||
[NOTE]
|
||||
.Why two minutes?
|
||||
====
|
||||
Spring Session will expire in 60 seconds, but the notification from Redis is not guaranteed to happen within 60 seconds.
|
||||
To ensure the socket is closed in a reasonable amount of time, Spring Session runs a background task every minute at 00 seconds that forcibly cleans up any expired sessions.
|
||||
This means you will need to wait at most two minutes before the WebSocket connection is terminated.
|
||||
====
|
||||
|
||||
Try accessing http://localhost:8080/
|
||||
You will be prompted to authenticate again.
|
||||
This demonstrates that the session properly expires.
|
||||
|
||||
Now repeat the same exercise, but instead of waiting two minutes send a message from *each* of the users every 30 seconds.
|
||||
You will see that the messages continue to be sent.
|
||||
Try accessing http://localhost:8080/
|
||||
You will not be prompted to authenticate again.
|
||||
This demonstrates the session is kept alive.
|
||||
|
||||
NOTE: Only messages sent from a user keep the session alive.
|
||||
This is because only messages coming from a user imply user activity.
|
||||
Messages received do not imply activity and thus do not renew the session expiration.
|
||||
366
docs/src/docs/asciidoc/index.adoc
Normal file
366
docs/src/docs/asciidoc/index.adoc
Normal file
@@ -0,0 +1,366 @@
|
||||
= Spring Session
|
||||
Rob Winch
|
||||
:doctype: book
|
||||
:indexdoc-tests: {docs-test-dir}docs/IndexDocTests.java
|
||||
:websocketdoc-test-dir: {docs-test-dir}docs/websocket/
|
||||
:toc: left
|
||||
|
||||
[[abstract]]
|
||||
|
||||
Spring Session provides an API and implementations for managing a user's session information.
|
||||
It also provides transparent integration with:
|
||||
|
||||
* HttpSession - allows for simple clustered sessions,
|
||||
obtaining the session from custom parts of the HTTP request (i.e. HTTP headers),
|
||||
and managing multiple user's sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google).
|
||||
|
||||
* WebSocket - provides the ability to keep the HttpSession alive when receiving WebSocket messages
|
||||
|
||||
[[samples]]
|
||||
= Samples and Guides (Start Here)
|
||||
|
||||
If you are looking to get started with Spring Session, the best place to start is our Sample Applications.
|
||||
|
||||
.Sample Applications
|
||||
|===
|
||||
| Source | Description | Guide
|
||||
|
||||
| {gh-samples-url}httpsession[HttpSession]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store.
|
||||
| link:guides/httpsession.html[HttpSession Guide]
|
||||
|
||||
| {gh-samples-url}boot[Spring Boot]
|
||||
| Demonstrates how to use Spring Session with Spring Boot.
|
||||
| link:guides/boot.html[Spring Boot]
|
||||
|
||||
| {gh-samples-url}security[Spring Security]
|
||||
| Demonstrates how to use Spring Session with an existing Spring Security application.
|
||||
| link:guides/security.html[Spring Security Guide]
|
||||
|
||||
| {gh-samples-url}rest[REST]
|
||||
| Demonstrates how to use Spring Session in a REST application to support authenticating with a header.
|
||||
| TBD
|
||||
|
||||
| {gh-samples-url}users[Multiple Sessions]
|
||||
| Demonstrates how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
|
||||
| link:guides/security.html[Users Guide]
|
||||
|
||||
| {gh-samples-url}websocket[WebSocket]
|
||||
| Demonstrates how to use Spring Session with WebSockets.
|
||||
| link:guides/websocket.html[WebSocket Guide]
|
||||
|
||||
[[samples-hazelcast]]
|
||||
| {gh-samples-url}hazelcast[Hazelcast]
|
||||
| Demonstrates how to use Spring Session with Hazelcast.
|
||||
| TBD
|
||||
|
||||
|===
|
||||
|
||||
[[httpsession]]
|
||||
= HttpSession Integration
|
||||
|
||||
Spring Session provides transparent integration with `HttpSession`.
|
||||
This means that developers can switch the `HttpSession` implementation out with an implementation that is backed by Spring Session.
|
||||
|
||||
== HttpSession Usage
|
||||
|
||||
Using Spring Session with `HttpSession` is enabled by adding a Servlet Filter before anything that uses the `HttpSession`.
|
||||
|
||||
The <<samples, HttpSession Sample>> provides a working sample on how to integrate Spring Session and `HttpSession`.
|
||||
You can the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession Guide when integrating with your own application:
|
||||
|
||||
include::guides/httpsession.adoc[tags=config,leveloffset=+1]
|
||||
|
||||
[[httpsession-how]]
|
||||
== How HttpSession Integration Works
|
||||
|
||||
Fortunately both `HttpSession` and `HttpServletRequest` (the API for obtaining an `HttpSession`) are both interfaces.
|
||||
This means that we can provide our own implementations for each of these APIs.
|
||||
|
||||
First we create a custom `HttpServletRequest` that returns a custom implementation of `HttpSession'.
|
||||
It looks something like the following:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
public SessionRepositoryRequestWrapper(HttpServletRequest original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
public HttpSession getSession() {
|
||||
return getSession(true);
|
||||
}
|
||||
|
||||
public HttpSession getSession(boolean createNew) {
|
||||
// create an HttpSession implementation from Spring Session
|
||||
}
|
||||
|
||||
// ... other methods delegate to the original HttpServletRequest ...
|
||||
}
|
||||
----
|
||||
|
||||
Any method that returns an `HttpSession` is overridden.
|
||||
All other methods are implemented by `HttpServletRequestWrapper` and simply delegate to the original `HttpServletRequest` implementation.
|
||||
|
||||
We replace the `HttpServletRequest` implementation using a servlet `Filter` called `SessionRepositoryFilter`.
|
||||
The pseudocode can be found below:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public class SessionRepositoryFilter implements Filter {
|
||||
|
||||
public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
SessionRepositoryRequestWrapper customRequest =
|
||||
new SessionRepositoryRequestWrapper(httpRequest);
|
||||
|
||||
chain.doFilter(customRequest, response, chain);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
By passing in a custom `HttpServletRequest` implementation into the `FilterChain` we ensure that anything invoked after our `Filter` uses the custom `HttpSession` implementation.
|
||||
This highlights why it is important that Spring Session's `SessionRepositoryFilter` must be placed before anything that interacts with the `HttpSession`.
|
||||
|
||||
== Multiple Sessions in Single Browser
|
||||
|
||||
Spring Session has the ability to support multiple sessions in a single browser instance.
|
||||
This provides the ability to support authenticating with multiple users in the same browser instance (i.e. Google Accounts).
|
||||
|
||||
The <<samples,Multiple Sessions Sample>> provides a complete working example of managing multiple users in the same browser instance.
|
||||
|
||||
include::guides/users.adoc[tags=how-does-it-work]
|
||||
|
||||
[[websocket]]
|
||||
= WebSocket Integration
|
||||
|
||||
Spring Session provides transparent integration with Spring's WebSocket support.
|
||||
|
||||
include::guides/websocket.adoc[tags=disclaimer]
|
||||
|
||||
[[websocket-why]]
|
||||
== Why Spring Session & WebSockets?
|
||||
|
||||
So why do we need Spring Session when using WebSockets?
|
||||
|
||||
Consider an email application that does much of its work through HTTP requests.
|
||||
However, there is also a chat application embedded within it that works over WebSocket APIs.
|
||||
If a user is actively chatting with someone, we should not timeout the `HttpSession` since this would be pretty poor user experience.
|
||||
However, this is exactly what https://java.net/jira/browse/WEBSOCKET_SPEC-175[JSR-356] does.
|
||||
|
||||
Another issue is that according to JSR-356 if the `HttpSession` times out any WebSocket that was created with that HttpSession and an authenticated user should be forcibly closed.
|
||||
This means that if we are actively chatting in our application and are not using the HttpSession, then we will also disconnect from our conversation!
|
||||
|
||||
[[websocket-usage]]
|
||||
== WebSocket Usage
|
||||
|
||||
The <<samples, WebSocket Sample>> provides a working sample on how to integrate Spring Session with WebSockets.
|
||||
You can the basic steps for integration below, but you are encouraged to follow along with the detailed WebSocket Guide when integrating with your own application:
|
||||
|
||||
=== HttpSession Integration
|
||||
|
||||
Before using WebSocket integration, you should be sure that you have <<httpsession>> working first.
|
||||
|
||||
include::guides/websocket.adoc[tags=config,leveloffset=+1]
|
||||
|
||||
|
||||
= API Documentation
|
||||
|
||||
== Session
|
||||
|
||||
A `Session` is a simplified `Map` of name value pairs.
|
||||
|
||||
Typical usage might look like the following:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{indexdoc-tests}[tags=repository-demo]
|
||||
----
|
||||
|
||||
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `Session`. The generic type is defined in our class.
|
||||
<2> We create a new `Session` using our `SessionRepository` and assign it to a variable of type `S`.
|
||||
<3> We interact with the `Session`. In our example, we demonstrate saving a `User` to the `Session`.
|
||||
<4> We now save the `Session`. This is why we needed the generic type `S`. The `SessionRepository` only allows saving `Session` instances that were created or retrieved using the same `SessionRepository`. This allows for the `SessionRepository` to make implementation specific optimizations (i.e. only writing attributes that have changed).
|
||||
<5> We retrieve the `Session` from the `SessionRepository`.
|
||||
<6> We obtain the persisted `User` from our `Session` without the need for explicitly casting our attribute.
|
||||
|
||||
== ExpiringSession
|
||||
|
||||
An `ExpiringSession` extends a `Session` by providing attributes related to the `Session` instance's expiration.
|
||||
If there is no need to interact with the expiration information, prefer using the more simple `Session` API.
|
||||
|
||||
Typical usage might look like the following:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{indexdoc-tests}[tags=expire-repository-demo]
|
||||
----
|
||||
|
||||
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `ExpiringSession`. The generic type is defined in our class.
|
||||
<2> We create a new `ExpiringSession` using our `SessionRepository` and assign it to a variable of type `S`.
|
||||
<3> We interact with the `ExpiringSession`.
|
||||
In our example, we demonstrate updating the amount of time the `ExpiringSession` can be inactive before it expires.
|
||||
<4> We now save the `ExpiringSession`.
|
||||
This is why we needed the generic type `S`.
|
||||
The `SessionRepository` only allows saving `ExpiringSession` instances that were created or retrieved using the same `SessionRepository`.
|
||||
This allows for the `SessionRepository` to make implementation specific optimizations (i.e. only writing attributes that have changed).
|
||||
The last accessed time is automatically updated when the `ExpiringSession` is saved.
|
||||
<5> We retrieve the `ExpiringSession` from the `SessionRepository`.
|
||||
If the `ExpiringSession` were expired, the result would be null.
|
||||
|
||||
== SessionRepository
|
||||
|
||||
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,HttpSession>> and <<websocket,WebSocket>> integration.
|
||||
|
||||
== RedisOperationsSessionRepository
|
||||
|
||||
`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` through `SessionMessageListener`.
|
||||
|
||||
=== Instantiating a RedisOperationsSessionRepository
|
||||
|
||||
A typical example of how to create a new instance can be seen below:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{indexdoc-tests}[tags=new-redisoperationssessionrepository]
|
||||
----
|
||||
|
||||
For additional information on how to create a `RedisConnectionFactory`, refer to the Spring Data Redis Reference.
|
||||
|
||||
=== Storage Details
|
||||
|
||||
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:<session-id> creationTime 1404360000000 \
|
||||
maxInactiveInterval 1800 lastAccessedTime 1404360000000 \
|
||||
sessionAttr:<attrName> someAttrValue sessionAttr:<attrName2> someAttrValue2
|
||||
|
||||
==== Session Expiration
|
||||
|
||||
An expiration is associated to each session using the EXPIRE command based upon the RedisOperationsSessionRepository.RedisSession.getMaxInactiveInterval().
|
||||
For example:
|
||||
|
||||
EXPIRE spring:session:sessions:<session-id> 1800
|
||||
|
||||
Each session expiration is also tracked to the nearest minute.
|
||||
This allows a background task to cleanup expired sessions in a deterministic fashion.
|
||||
For example:
|
||||
|
||||
SADD spring:session:expirations:<expire-rounded-up-to-nearest-minute> <session-id>
|
||||
EXPIRE spring:session:expirations:<expire-rounded-up-to-nearest-minute> 1800
|
||||
|
||||
The Redis expiration is still placed on each key to ensure that if the server is down when the session expires, it is still cleaned up.
|
||||
|
||||
==== Optimized Writes
|
||||
|
||||
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:<session-id> sessionAttr:<attrName2> newValue
|
||||
EXPIRE spring:session:sessions:<session-id> 1800
|
||||
|
||||
=== SessionDestroyedEvent
|
||||
|
||||
`RedisOperationsSessionRepository` supports firing a `SessionDestroyedEvent` whenever a `Session` is deleted or 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 a `SessionDestroyedEvent` is made available through the `SessionMessageListener` which listens to http://redis.io/topics/notifications[Redis Keyspace events].
|
||||
In order for this to work, Redis Keyspace events for Generic commands and Expired events needs to be enabled.
|
||||
For example:
|
||||
|
||||
TIP: If you are using `@EnableRedisHttpSession` the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
redis-cli config set notify-keyspace-events Egx
|
||||
----
|
||||
|
||||
=== Viewing the Session in Redis
|
||||
|
||||
After http://redis.io/topics/quickstart[installing redis-cli], you can inspect the values in Redis http://redis.io/commands#hash[using the redis-cli].
|
||||
For example, enter the following into a terminal:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ 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> The suffix of this key is the session identifier of the Spring Session.
|
||||
<2> This key contains all the session ids that should be deleted at the time `1418772300000`.
|
||||
|
||||
You can also view the attributes of each session.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
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
|
||||
|
||||
The `MapSessionRepository` allows for persisting `ExpiringSession` in a `Map` with the key being the `ExpiringSession` id and the value being the `ExpiringSession`.
|
||||
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.
|
||||
|
||||
=== Instantiating MapSessionRepository
|
||||
|
||||
Creating a new instance is as simple as:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{indexdoc-tests}[tags=new-mapsessionrepository]
|
||||
----
|
||||
|
||||
=== Using Spring Session and Hazlecast
|
||||
|
||||
The <<samples,Hazelcast Sample>> is a complete application demonstrating using Spring Session with Hazelcast.
|
||||
To run it use the following:
|
||||
|
||||
./gradlew :samples:hazelcast:tomcatRun
|
||||
|
||||
= Spring Session Community
|
||||
|
||||
We are glad to consider you a part of our community.
|
||||
Please find additional information below.
|
||||
|
||||
== Support
|
||||
|
||||
You can get help by asking questions on http://stackoverflow.com/questions/tagged/spring-session[StackOverflow with the tag spring-session].
|
||||
Similarly we encourage helping others by answering questions on StackOverflow.
|
||||
|
||||
== Source Code
|
||||
|
||||
Our source code can be found on github at https://github.com/spring-projects/spring-session/
|
||||
|
||||
== Issue Tracking
|
||||
|
||||
We track issues in github issues at https://github.com/spring-projects/spring-session/issues
|
||||
|
||||
== Contributing
|
||||
|
||||
We appreciate https://help.github.com/articles/using-pull-requests/[Pull Requests].
|
||||
|
||||
== License
|
||||
|
||||
Spring Session is Open Source software released under the http://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
|
||||
110
docs/src/test/java/docs/IndexDocTests.java
Normal file
110
docs/src/test/java/docs/IndexDocTests.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package docs;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.session.*;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class IndexDocTests {
|
||||
static final String ATTR_USER = "user";
|
||||
|
||||
@Test
|
||||
public void repositoryDemo() {
|
||||
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
|
||||
demo.repository = new MapSessionRepository();
|
||||
|
||||
demo.demo();
|
||||
}
|
||||
|
||||
// tag::repository-demo[]
|
||||
public class RepositoryDemo<S extends Session> {
|
||||
private SessionRepository<S> repository; // <1>
|
||||
|
||||
public void demo() {
|
||||
S toSave = repository.createSession(); // <2>
|
||||
|
||||
// <3>
|
||||
User rwinch = new User("rwinch");
|
||||
toSave.setAttribute(ATTR_USER, rwinch);
|
||||
|
||||
repository.save(toSave); // <4>
|
||||
|
||||
S session = repository.getSession(toSave.getId()); // <5>
|
||||
|
||||
// <6>
|
||||
User user = session.getAttribute(ATTR_USER);
|
||||
assertThat(user).isEqualTo(rwinch);
|
||||
}
|
||||
|
||||
// ... setter methods ...
|
||||
}
|
||||
// end::repository-demo[]
|
||||
|
||||
|
||||
@Test
|
||||
public void expireRepositoryDemo() {
|
||||
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
|
||||
demo.repository = new MapSessionRepository();
|
||||
|
||||
demo.demo();
|
||||
}
|
||||
|
||||
// tag::expire-repository-demo[]
|
||||
public class ExpiringRepositoryDemo<S extends ExpiringSession> {
|
||||
private SessionRepository<S> repository; // <1>
|
||||
|
||||
public void demo() {
|
||||
S toSave = repository.createSession(); // <2>
|
||||
// ...
|
||||
toSave.setMaxInactiveInterval(30); // <3>
|
||||
|
||||
repository.save(toSave); // <4>
|
||||
|
||||
S session = repository.getSession(toSave.getId()); // <5>
|
||||
// ...
|
||||
}
|
||||
|
||||
// ... setter methods ...
|
||||
}
|
||||
// end::expire-repository-demo[]
|
||||
|
||||
@Test
|
||||
public void newRedisOperationsSessionRepository() {
|
||||
// tag::new-redisoperationssessionrepository[]
|
||||
JedisConnectionFactory factory = new JedisConnectionFactory();
|
||||
SessionRepository<? extends ExpiringSession> repository =
|
||||
new RedisOperationsSessionRepository(factory);
|
||||
// end::new-redisoperationssessionrepository[]
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapRepository() {
|
||||
// tag::new-mapsessionrepository[]
|
||||
SessionRepository<? extends ExpiringSession> repository = new MapSessionRepository();
|
||||
// end::new-mapsessionrepository[]
|
||||
}
|
||||
|
||||
private static class User {
|
||||
private User(String username) {}
|
||||
}
|
||||
}
|
||||
46
docs/src/test/java/docs/websocket/WebSocketConfig.java
Normal file
46
docs/src/test/java/docs/websocket/WebSocketConfig.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package docs.websocket;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
// tag::class[]
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig
|
||||
extends AbstractWebSocketMessageBrokerConfigurer {
|
||||
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/messages")
|
||||
.withSockJS();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
30
docs/src/test/java/docs/websocket/WebSocketDocTests.java
Normal file
30
docs/src/test/java/docs/websocket/WebSocketDocTests.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package docs.websocket;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class WebSocketDocTests {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user