Compare commits

..

62 Commits

Author SHA1 Message Date
Spring Operator
144d3d4103 URL Cleanup (#1384)
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed But Review Recommended
These URLs were fixed, but the https status was not OK. However, the https status was the same as the http request or http redirected to an https URL, so they were migrated. Your review is recommended.

* [ ] http://www.puppycrawl.com/dtds/configuration_1_3.dtd (404) with 1 occurrences migrated to:
  https://www.puppycrawl.com/dtds/configuration_1_3.dtd ([https](https://www.puppycrawl.com/dtds/configuration_1_3.dtd) result 404).
* [ ] http://www.puppycrawl.com/dtds/suppressions_1_1.dtd (404) with 1 occurrences migrated to:
  https://www.puppycrawl.com/dtds/suppressions_1_1.dtd ([https](https://www.puppycrawl.com/dtds/suppressions_1_1.dtd) result 404).

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* [ ] http://www.springframework.org/schema/beans/spring-beans.xsd with 13 occurrences migrated to:
  https://www.springframework.org/schema/beans/spring-beans.xsd ([https](https://www.springframework.org/schema/beans/spring-beans.xsd) result 200).
* [ ] http://www.springframework.org/schema/context/spring-context.xsd with 10 occurrences migrated to:
  https://www.springframework.org/schema/context/spring-context.xsd ([https](https://www.springframework.org/schema/context/spring-context.xsd) result 200).
* [ ] http://www.springframework.org/schema/gemfire/spring-gemfire.xsd with 4 occurrences migrated to:
  https://www.springframework.org/schema/gemfire/spring-gemfire.xsd ([https](https://www.springframework.org/schema/gemfire/spring-gemfire.xsd) result 200).
* [ ] http://www.springframework.org/schema/jdbc/spring-jdbc.xsd with 1 occurrences migrated to:
  https://www.springframework.org/schema/jdbc/spring-jdbc.xsd ([https](https://www.springframework.org/schema/jdbc/spring-jdbc.xsd) result 200).
* [ ] http://www.springframework.org/schema/security/spring-security.xsd with 2 occurrences migrated to:
  https://www.springframework.org/schema/security/spring-security.xsd ([https](https://www.springframework.org/schema/security/spring-security.xsd) result 200).
* [ ] http://www.springframework.org/schema/util/spring-util-4.1.xsd with 2 occurrences migrated to:
  https://www.springframework.org/schema/util/spring-util-4.1.xsd ([https](https://www.springframework.org/schema/util/spring-util-4.1.xsd) result 200).
* [ ] http://www.springframework.org/schema/util/spring-util.xsd with 4 occurrences migrated to:
  https://www.springframework.org/schema/util/spring-util.xsd ([https](https://www.springframework.org/schema/util/spring-util.xsd) result 200).
* [ ] http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd with 4 occurrences migrated to:
  https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd ([https](https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd) result 302).

# Ignored
These URLs were intentionally ignored.

* http://java.sun.com/xml/ns/javaee with 8 occurrences
* http://www.hazelcast.com/schema/config with 4 occurrences
* http://www.springframework.org/schema/beans with 26 occurrences
* http://www.springframework.org/schema/context with 20 occurrences
* http://www.springframework.org/schema/gemfire with 8 occurrences
* http://www.springframework.org/schema/jdbc with 2 occurrences
* http://www.springframework.org/schema/p with 8 occurrences
* http://www.springframework.org/schema/security with 4 occurrences
* http://www.springframework.org/schema/util with 12 occurrences
* http://www.w3.org/2001/XMLSchema-instance with 19 occurrences
2019-04-01 10:23:59 -05:00
Spring Operator
fc9bb33414 URL Cleanup (#1390)
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# HTTP URLs that Could Not Be Fixed
These URLs were unable to be fixed. Please review them to see if they can be manually resolved.

* [ ] http://www.faqs.org/qa/rfcc-1940.html (200) with 3 occurrences could not be migrated:
   ([https](https://www.faqs.org/qa/rfcc-1940.html) result AnnotatedConnectException).
* [ ] http://www.faqs.org/rfcs/rfc3548.html (200) with 3 occurrences could not be migrated:
   ([https](https://www.faqs.org/rfcs/rfc3548.html) result AnnotatedConnectException).
* [ ] http://www.faqs.org/ (301) with 1 occurrences could not be migrated:
   ([https](https://www.faqs.org/) result AnnotatedConnectException).
* [ ] http://www.somehost.com/some/path (302) with 4 occurrences could not be migrated:
   ([https](https://www.somehost.com/some/path) result ConnectTimeoutException).
* [ ] http://www.somehost.com/some/path?%s=5 (302) with 2 occurrences could not be migrated:
   ([https](https://www.somehost.com/some/path?%s=5) result ConnectTimeoutException).
* [ ] http://www.somehost.com/some/path?a=b (302) with 4 occurrences could not be migrated:
   ([https](https://www.somehost.com/some/path?a=b) result ConnectTimeoutException).
* [ ] http://www.somehost.com/some/path?a=b&%s=5&c=d (302) with 2 occurrences could not be migrated:
   ([https](https://www.somehost.com/some/path?a=b&%s=5&c=d) result ConnectTimeoutException).
* [ ] http://iharder.net/base64 (303) with 2 occurrences could not be migrated:
   ([https](https://iharder.net/base64) result AnnotatedConnectException).

# Fixed URLs

## Fixed But Review Recommended
These URLs were fixed, but the https status was not OK. However, the https status was the same as the http request or http redirected to an https URL, so they were migrated. Your review is recommended.

* [ ] http://www.ultraq.net.nz/thymeleaf/layout (302) with 14 occurrences migrated to:
  https://github.com/ultraq/thymeleaf-layout-dialect ([https](https://www.ultraq.net.nz/thymeleaf/layout) result ConnectTimeoutException).
* [ ] http://192.168.1.100:8080/ (AnnotatedConnectException) with 1 occurrences migrated to:
  https://192.168.1.100:8080/ ([https](https://192.168.1.100:8080/) result ConnectTimeoutException).
* [ ] http://www.faqs (UnknownHostException) with 1 occurrences migrated to:
  https://www.faqs ([https](https://www.faqs) result UnknownHostException).
* [ ] http://html5shim.googlecode.com/svn/trunk/html5.js (404) with 1 occurrences migrated to:
  https://html5shim.googlecode.com/svn/trunk/html5.js ([https](https://html5shim.googlecode.com/svn/trunk/html5.js) result 404).

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* [ ] http://creativecommons.org/licenses/by/3.0/ with 1 occurrences migrated to:
  https://creativecommons.org/licenses/by/3.0/ ([https](https://creativecommons.org/licenses/by/3.0/) result 200).
* [ ] http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html with 2 occurrences migrated to:
  https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html ([https](https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html) result 200).
* [ ] http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html with 1 occurrences migrated to:
  https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html ([https](https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html) result 200).
* [ ] http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html with 1 occurrences migrated to:
  https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html ([https](https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html) result 200).
* [ ] http://docs.hazelcast.org/docs/latest/manual/html-single/index.html with 8 occurrences migrated to:
  https://docs.hazelcast.org/docs/latest/manual/html-single/index.html ([https](https://docs.hazelcast.org/docs/latest/manual/html-single/index.html) result 200).
* [ ] http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html with 1 occurrences migrated to:
  https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html ([https](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html) result 200).
* [ ] http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ with 4 occurrences migrated to:
  https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ ([https](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/) result 200).
* [ ] http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/ with 6 occurrences migrated to:
  https://docs.spring.io/spring-data-gemfire/docs/current/reference/html/ ([https](https://docs.spring.io/spring-data-gemfire/docs/current/reference/html/) result 200).
* [ ] http://docs.spring.io/spring-data-redis/docs/current/reference/html/ with 1 occurrences migrated to:
  https://docs.spring.io/spring-data-redis/docs/current/reference/html/ ([https](https://docs.spring.io/spring-data-redis/docs/current/reference/html/) result 200).
* [ ] http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/ with 5 occurrences migrated to:
  https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/ ([https](https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/) result 200).
* [ ] http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html with 4 occurrences migrated to:
  https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html ([https](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html) result 200).
* [ ] http://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/ with 1 occurrences migrated to:
  https://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/ ([https](https://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/) result 200).
* [ ] http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/ with 2 occurrences migrated to:
  https://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/ ([https](https://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/) result 200).
* [ ] http://docs.spring.io/spring-session/docs/current/reference/html5/ with 1 occurrences migrated to:
  https://docs.spring.io/spring-session/docs/current/reference/html5/ ([https](https://docs.spring.io/spring-session/docs/current/reference/html5/) result 200).
* [ ] http://getbootstrap.com/getting-started/ with 1 occurrences migrated to:
  https://getbootstrap.com/getting-started/ ([https](https://getbootstrap.com/getting-started/) result 200).
* [ ] http://hazelcast.org/ with 1 occurrences migrated to:
  https://hazelcast.org/ ([https](https://hazelcast.org/) result 200).
* [ ] http://infinispan.org/docs/dev/user_guide/user_guide.html with 1 occurrences migrated to:
  https://infinispan.org/docs/dev/user_guide/user_guide.html ([https](https://infinispan.org/docs/dev/user_guide/user_guide.html) result 200).
* [ ] http://logback.qos.ch/manual/groovy.html with 1 occurrences migrated to:
  https://logback.qos.ch/manual/groovy.html ([https](https://logback.qos.ch/manual/groovy.html) result 200).
* [ ] http://projects.spring.io/spring-session/ with 1 occurrences migrated to:
  https://projects.spring.io/spring-session/ ([https](https://projects.spring.io/spring-session/) result 200).
* [ ] http://redis.io/commands with 1 occurrences migrated to:
  https://redis.io/commands ([https](https://redis.io/commands) result 200).
* [ ] http://redis.io/commands/expire with 1 occurrences migrated to:
  https://redis.io/commands/expire ([https](https://redis.io/commands/expire) result 200).
* [ ] http://redis.io/commands/hmset with 1 occurrences migrated to:
  https://redis.io/commands/hmset ([https](https://redis.io/commands/hmset) result 200).
* [ ] http://redis.io/download with 10 occurrences migrated to:
  https://redis.io/download ([https](https://redis.io/download) result 200).
* [ ] http://redis.io/topics/data-types with 1 occurrences migrated to:
  https://redis.io/topics/data-types ([https](https://redis.io/topics/data-types) result 200).
* [ ] http://redis.io/topics/notifications with 5 occurrences migrated to:
  https://redis.io/topics/notifications ([https](https://redis.io/topics/notifications) result 200).
* [ ] http://redis.io/topics/quickstart with 7 occurrences migrated to:
  https://redis.io/topics/quickstart ([https](https://redis.io/topics/quickstart) result 200).
* [ ] http://stackoverflow.com with 1 occurrences migrated to:
  https://stackoverflow.com ([https](https://stackoverflow.com) result 200).
* [ ] http://stackoverflow.com/questions/tagged/spring-session with 1 occurrences migrated to:
  https://stackoverflow.com/questions/tagged/spring-session ([https](https://stackoverflow.com/questions/tagged/spring-session) result 200).
* [ ] http://stackoverflow.com/tags/spring-session with 1 occurrences migrated to:
  https://stackoverflow.com/tags/spring-session ([https](https://stackoverflow.com/tags/spring-session) result 200).
* [ ] http://www.thymeleaf.org with 15 occurrences migrated to:
  https://www.thymeleaf.org ([https](https://www.thymeleaf.org) result 200).
* [ ] http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd with 7 occurrences migrated to:
  https://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd ([https](https://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd) result 200).
* [ ] http://contributor-covenant.org with 1 occurrences migrated to:
  https://contributor-covenant.org ([https](https://contributor-covenant.org) result 301).
* [ ] http://contributor-covenant.org/version/1/3/0/ with 1 occurrences migrated to:
  https://contributor-covenant.org/version/1/3/0/ ([https](https://contributor-covenant.org/version/1/3/0/) result 301).
* [ ] http://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html with 5 occurrences migrated to:
  https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html ([https](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html) result 301).
* [ ] http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ with 4 occurrences migrated to:
  https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ ([https](https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/getting_started/installation/install_intro.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/getting_started/installation/install_intro.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/getting_started/installation/install_intro.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/function_exec/chapter_overview.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/function_exec/chapter_overview.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/function_exec/chapter_overview.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html with 5 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/creating_map_indexes.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/creating_map_indexes.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/creating_map_indexes.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/product_intro.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/product_intro.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/product_intro.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html with 3 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html with 5 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html with 4 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/multi_site_configuration/chapter_overview.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/multi_site_configuration/chapter_overview.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/multi_site_configuration/chapter_overview.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/p2p_configuration/chapter_overview.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/p2p_configuration/chapter_overview.html ([https](https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/p2p_configuration/chapter_overview.html) result 302).
* [ ] http://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html with 1 occurrences migrated to:
  https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html ([https](https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html) result 302).
* [ ] http://spring.io/spring-security with 4 occurrences migrated to:
  https://spring.io/spring-security ([https](https://spring.io/spring-security) result 302).
* [ ] http://www.maxmind.com with 3 occurrences migrated to:
  https://www.maxmind.com ([https](https://www.maxmind.com) result 302).

# Ignored
These URLs were intentionally ignored.

* http://java.sun.com/jsp/jstl/core with 14 occurrences
* http://localhost:8080/ with 45 occurrences
* http://localhost:8080/?_s=0 with 1 occurrences
* http://localhost:8080/?_s=1 with 2 occurrences
* http://localhost:8080/h2-console/ with 3 occurrences
* http://localhost:8080/logout with 1 occurrences
* http://localhost:8080/test/index with 2 occurrences
* http://localhost:xxxxx/hazelcast/rest/maps/spring:session:sessions/7e8383a4-082c-4ffe-a4bc-c40fd3363c5e with 1 occurrences
* http://www.w3.org/1999/xhtml with 7 occurrences
* http://www.webjars.org/tags with 14 occurrences
2019-04-01 10:22:36 -05:00
Spring Operator
ec4a0cb9ff URL Cleanup
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* http://www.apache.org/licenses/LICENSE-2.0 with 325 occurrences migrated to:
  https://www.apache.org/licenses/LICENSE-2.0 ([https](https://www.apache.org/licenses/LICENSE-2.0) result 200).
* http://www.apache.org/licenses/LICENSE-2.0.html with 2 occurrences migrated to:
  https://www.apache.org/licenses/LICENSE-2.0.html ([https](https://www.apache.org/licenses/LICENSE-2.0.html) result 200).
2019-03-14 20:38:12 -05:00
Rob Winch
e5274b6eae URL Cleanup
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortner).

# HTTP URLs that Could Not Be Fixed
These URLs were unable to be fixed. Please review them to see if they can be manually resolved.

* http://aopalliance.sourceforge.net/doc/ (200) migrated to:
  http://aopalliance.sourceforge.net/doc/ ([https](https://aopalliance.sourceforge.net/doc/) result AnnotatedConnectException).
* http://dist.gemstone.com/maven/release (404) migrated to:
  http://dist.gemstone.com/maven/release ([https](https://dist.gemstone.com/maven/release) result SSLHandshakeException).

# Fixed URLs

## Fixed But Review Recommended
These URLs were fixed, but the https status was not OK. However, the https status was the same as the http request or http redirected to an https URL, so they were migrated. Your review is recommended.

* http://www.quartz-scheduler.org/api/2.2.0/ (404) migrated to:
  https://www.quartz-scheduler.org/api/2.2.0/ ([https](https://www.quartz-scheduler.org/api/2.2.0/) result 404).

## Fixed Success
These URLs were fixed successfully.

* http://commons.apache.org/proper/commons-codec/apidocs/ migrated to:
  https://commons.apache.org/proper/commons-codec/apidocs/ ([https](https://commons.apache.org/proper/commons-codec/apidocs/) result 200).
* http://commons.apache.org/proper/commons-dbcp/apidocs/ migrated to:
  https://commons.apache.org/proper/commons-dbcp/apidocs/ ([https](https://commons.apache.org/proper/commons-dbcp/apidocs/) result 200).
* http://commons.apache.org/proper/commons-lang/javadocs/api-2.5/ migrated to:
  https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/ ([https](https://commons.apache.org/proper/commons-lang/javadocs/api-2.5/) result 200).
* http://docs.jboss.org/jbossas/javadoc/4.0.5/connector/ migrated to:
  https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/ ([https](https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/) result 200).
* http://docs.jboss.org/jbossas/javadoc/7.1.2.Final/ migrated to:
  https://docs.jboss.org/jbossas/javadoc/7.1.2.Final/ ([https](https://docs.jboss.org/jbossas/javadoc/7.1.2.Final/) result 200).
* http://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/ migrated to:
  https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/ ([https](https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/) result 200).
* http://docs.oracle.com/javaee/7/api/ migrated to:
  https://docs.oracle.com/javaee/7/api/ ([https](https://docs.oracle.com/javaee/7/api/) result 200).
* http://docs.oracle.com/javase/8/docs/api/ migrated to:
  https://docs.oracle.com/javase/8/docs/api/ ([https](https://docs.oracle.com/javase/8/docs/api/) result 200).
* http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/ migrated to:
  https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/ ([https](https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/) result 200).
* http://portals.apache.org/pluto/portlet-2.0-apidocs/ migrated to:
  https://portals.apache.org/pluto/portlet-2.0-apidocs/ ([https](https://portals.apache.org/pluto/portlet-2.0-apidocs/) result 200).
* http://tiles.apache.org/framework/apidocs/ migrated to:
  https://tiles.apache.org/framework/apidocs/ ([https](https://tiles.apache.org/framework/apidocs/) result 200).
* http://tiles.apache.org/tiles-request/apidocs/ migrated to:
  https://tiles.apache.org/tiles-request/apidocs/ ([https](https://tiles.apache.org/tiles-request/apidocs/) result 200).
* http://www.apache.org/licenses/LICENSE-2.0.txt migrated to:
  https://www.apache.org/licenses/LICENSE-2.0.txt ([https](https://www.apache.org/licenses/LICENSE-2.0.txt) result 200).
* http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/ migrated to:
  https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/ ([https](https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/) result 200).
* http://fasterxml.github.com/jackson-core/javadoc/2.3.0/ migrated to:
  https://fasterxml.github.com/jackson-core/javadoc/2.3.0/ ([https](https://fasterxml.github.com/jackson-core/javadoc/2.3.0/) result 301).
* http://fasterxml.github.com/jackson-databind/javadoc/2.3.0/ migrated to:
  https://fasterxml.github.com/jackson-databind/javadoc/2.3.0/ ([https](https://fasterxml.github.com/jackson-databind/javadoc/2.3.0/) result 301).
* http://glassfish.java.net/nonav/docs/v3/api/ migrated to:
  https://glassfish.java.net/nonav/docs/v3/api/ ([https](https://glassfish.java.net/nonav/docs/v3/api/) result 301).
* http://pic.dhe.ibm.com/infocenter/wasinfo/v7r0/topic/com.ibm.websphere.javadoc.doc/web/apidocs/ migrated to:
  https://pic.dhe.ibm.com/infocenter/wasinfo/v7r0/topic/com.ibm.websphere.javadoc.doc/web/apidocs/ ([https](https://pic.dhe.ibm.com/infocenter/wasinfo/v7r0/topic/com.ibm.websphere.javadoc.doc/web/apidocs/) result 301).
* http://projects.spring.io/spring-session migrated to:
  https://projects.spring.io/spring-session ([https](https://projects.spring.io/spring-session) result 301).
2019-03-05 08:31:17 -06:00
Spring Buildmaster
3db2e13d7e Next development version 2019-01-11 05:02:13 +00:00
Spring Buildmaster
774e6df063 Release version 1.3.5.RELEASE 2019-01-11 05:02:06 +00:00
Rob Winch
6911bd359b Update to Spring 4.3.19.RELEASE
Fixes: gh-1310
2019-01-10 22:00:27 -06:00
Rob Winch
ddae03c79e Update to Spring Security 4.2.11.RELEASE
Fixes: gh-1309
2019-01-10 21:45:49 -06:00
Vedran Pavic
bba095f276 Fix Spring Security integration docs sample
Resolves: #1306
2019-01-10 13:54:45 +01:00
Rob Winch
61937a9251 Fix SessionRepositoryFilter Javadoc 2018-12-14 09:45:41 -06:00
Jeff
3885b7e7ff Fix RedisOperationsSessionRepository javadoc typos
Resolves: #1276
2018-12-04 20:08:50 +01:00
Vedran Pavic
bea569c3c8 Make SessionUpdateEntryProcessor public
Resolves: #1266
2018-11-26 19:21:55 +01:00
Spring Buildmaster
7f1434cc4c Next development version 2018-11-14 16:51:17 +00:00
Spring Buildmaster
cd8f87e0a9 Release version 1.3.4.RELEASE 2018-11-14 16:51:11 +00:00
Vedran Pavic
95f41a7024 Polish 2018-11-08 22:32:46 +01:00
Vedran Pavic
d245cc1a36 Polish contribution
Resolves: #1250
2018-11-08 22:00:52 +01:00
Josh Cummings
695f2f1509 Commit Session on Include Dispatch
The servlet spec disallows any writing of headers after an include has been issued.

This commit intercepts the include and commits the session, then
allowing the include to proceed.

See: #1250
2018-11-08 22:00:50 +01:00
Vedran Pavic
3940a22d5e Disable network join in Hazelcast samples 2018-09-26 13:53:44 +02:00
Vedran Pavic
eb4ce12915 Upgrade dependencies and samples to Spring Boot 1.4.7.RELEASE levels
Resolves: #1108
2018-09-26 12:25:08 +02:00
Vedran Pavic
46bac131d0 Configure default LobHandler to use temporary LOBs on Oracle
JdbcOperationsSessionRepository recently introduced validation when inserting new session attributes in order to prevent data integrity violations in highly concurrent environments. This is done by using INSERT INTO ... SELECT statement to verify existence of session record in parent table. Such arrangement causes problems with Oracle if inserted attribute is of size 4 kb or more.

This commit enhances JdbcHttpSessionConfiguration to detect Oracle database is used, and set createTemporaryLob option on default LobHandler to true.

Resolves: #1212
2018-09-26 06:36:06 +02:00
Vedran Pavic
9675278729 Fix SpringSessionRememberMeServices documentation example
Resolves: #1211
2018-09-26 06:08:57 +02:00
Vedran Pavic
f0c216d9d5 Improve support for Hazelcast client-server topology
See: #1130
2018-09-26 06:01:42 +02:00
Vedran Pavic
cb6f7fdfa6 Insert new attributes conditionally in JDBC repo
At present, the insert of new attributes in JdbcOperationsSessionRepository is done unconditionally. This can cause data integrity violation errors with concurrent requests, where one request attempts to add new session attribute while the other, concurrent request, deletes the session.

This commit addresses the described scenario by executing insert of new attributes conditionally on presence of parent record.

Closes gh-1153
2018-08-13 08:21:34 +02:00
Vedran Pavic
b50a4e247e Improve support for Hazelcast client-server topology
This commit improves support for use of Spring Session with Hazelcast's client-server topology by ensuring SessionUpdateEntryProcessor is easier to serialize to the cluster. This is done by refactoring SessionUpdateEntryProcessor from static inner class of HazelcastSessionRepository to a dedicated class, therefore minimizing the dependencies to other Spring Session components.

Closes gh-1130
2018-08-03 17:16:55 +02:00
Vedran Pavic
6b3d78ac09 Disable network join in Hazelcast integration tests 2018-08-03 17:16:52 +02:00
Vedran Pavic
c0bd38c46f Improve HazelcastSessionRepository write operations
Closes gh-1106
2018-08-03 13:06:13 +02:00
Vedran Pavic
2262600b21 Improve update handling in HazelcastSessionRepository
See gh-1106
2018-08-03 13:05:29 +02:00
Vedran Pavic
0c11a4297a Add logging for errors decoding Base64 cookies
Closes gh-1134
2018-08-02 19:01:10 +02:00
Vedran Pavic
b778d97dc7 Ensure Session#getAttributeNames implementations return a copy
Currently, Session#getAttributeNames implementations, by delegating to MapSession, all return a session attribute map's key set. This causes ConcurrentModificationException when an attempt to modify session attributes is made while iterating over the returned attribute names.

Closes gh-1129
2018-08-02 18:59:29 +02:00
Spring Buildmaster
d0887fe40d Next development version 2018-05-08 18:25:17 +00:00
Spring Buildmaster
1a94d742b1 Release version 1.3.3.RELEASE 2018-05-08 18:25:07 +00:00
Vedran Pavic
c433b01ee5 Optimize session retrieval in JdbcOperationsSessionRepository
Previously, SessionResultSetExtractor used JdbcSession.setAttribute which had a side effect of freshly loaded session potentially having a non-empty delta and/or changed flag set. This commit optimizes session retrieval to invoke setAttribute directly on the delegate, therefore preventing unnecessary modifications of delta and change flags.

Closes gh-1053
2018-04-16 10:33:55 +02:00
Vedran Pavic
a3195f1f4b Fix NPE in RedisOperationsSessionRepository event handling
Closes gh-1049
2018-04-16 10:33:55 +02:00
Vedran Pavic
467ecaaeff Harmonize config locations 2018-04-16 10:33:54 +02:00
Spring Buildmaster
4a18242d95 Next development version 2018-02-09 20:11:48 +00:00
Spring Buildmaster
e44cd45668 Release version 1.3.2.RELEASE 2018-02-09 20:11:39 +00:00
Vedran Pavic
7f0de8126e Improve layout of community extensions doc section
Closes gh-993
2018-02-05 12:43:25 +01:00
Vedran Pavic
d75b03f594 Fix Gradle deprecation warnings 2018-01-29 18:45:38 +01:00
Vedran Pavic
ca0fea3a54 Upgrade Gradle to 3.5.1 2018-01-29 18:36:40 +01:00
Eddú Meléndez
92af786e6b Polish samples
Closes gh-986
2018-01-29 18:17:42 +01:00
Vedran Pavic
d271a4ad1d Polish contribution
Closes gh-920
2018-01-29 18:12:01 +01:00
Roman Cherepanov
04c1908378 Fix link to GemFire documentation
See gh-920
2018-01-29 18:11:33 +01:00
Vedran Pavic
f0e187fbd7 Update references to Spring Boot's Redis starter
Closes gh-952
2018-01-29 17:06:51 +01:00
Vedran Pavic
2d3001a24e Fix SessionRepositoryFilter not retaining original maxInactiveInterval
Closes gh-951
2018-01-29 16:45:46 +01:00
Vedran Pavic
f2d1badd60 Fix misc typos
Closes gh-985
2018-01-29 16:27:20 +01:00
Vedran Pavic
6a6d60d8f8 Improve session event handling
Closes gh-984
2018-01-29 16:27:20 +01:00
Vedran Pavic
b2cb3f6a3a Optimize HazelcastSessionRepository write operations
Closes gh-983
2018-01-29 16:27:20 +01:00
Vedran Pavic
d7ae5785eb Improve JDBC data store schema scripts
Closes gh-982
2018-01-29 16:27:15 +01:00
Kanjie Lu
96eb40439c fix typo
change "they key" to  "the key"

Closes gh-981
2018-01-29 16:26:08 +01:00
mikemassa84
8ed0999ad3 Update grails3.adoc
Add a note about spring-session and grails flash scope, with link to stackoverflow answer.

Closes gh-980
2018-01-29 16:26:08 +01:00
Vedran Pavic
b2d2335d73 Replace StringBuffer usages with StringBuilder
Closes gh-979
2018-01-29 16:25:59 +01:00
Spring Buildmaster
f597c5a824 Next development version 2017-04-27 18:57:32 +00:00
Spring Buildmaster
212eca306c Release version 1.3.1.RELEASE 2017-04-27 18:57:22 +00:00
Vedran Pavic
a64e6d1a9c Use explicit constraints in JDBC schema scripts
Fixes gh-765
2017-04-26 23:33:35 +02:00
Vedran Pavic
598715f219 Remove logging for "Skip invoking on" response committed
Fixes gh-764
2017-04-26 23:18:00 +02:00
Sebastian Laskawiec
4d90fcc7a8 Add link to Infinispan for Spring Session documentation
Fixes gh-763
2017-04-26 23:17:33 +02:00
John Blum
4f57c6c6c1 Fix invalid not null assertions
Fixes gh-762
2017-04-26 23:16:55 +02:00
Vedran Pavic
c210a4a3cf Prevent NPE inMongoOperationsSessionRepository when creating session if max inactive interval is undefined
Fixes gh-761
2017-04-26 23:15:40 +02:00
Rob Winch
a1380d722b SpringSessionRememberMeServices rm SecurityContext attribute
SpringSessionRememberMeServices use to invalidate the session which would
cause Spring Security's saved request to be lost.

Now SpringSessionRememberMeServices deletes the SecurityContext from the
HttpSession instead.

Fixes gh-752
2017-04-26 09:09:12 -05:00
Rob Winch
c029922bf4 Update to Spring Data Redis 1.7.10
Note the Spring 3 tests must stick to 1.7.1

Fixes gh-756
2017-04-26 07:56:15 -05:00
John Blum
6b55f3f7d2 Restore proper behavior of HttpSession created events in GemFire support when client Region is a PROXY in the client/server topology
Fixes gh-757

(cherry picked from commit c0c404ab96)
Signed-off-by: John Blum <jblum@pivotal.io>
2017-04-25 20:41:13 -07:00
John Blum
6668e41b0a Improve GemFire SessionRepository, Session copy logic to avoid issues with delta propagation on updates
Upgrade to Spring Data GemFire 1.8.10.RELEASE

Fixes #gh-755
2017-04-21 15:48:40 -07:00
719 changed files with 38792 additions and 22063 deletions

View File

@@ -1,19 +0,0 @@
root = true
[*]
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120
[*.java]
indent_style = tab
indent_size = 4
charset = latin1
continuation_indent_size = 8
[*.xml]
indent_style = tab
indent_size = 4
charset = latin1
continuation_indent_size = 8

View File

@@ -1,7 +1,3 @@
<!--
For Security Vulnerabilities, please use https://pivotal.io/security#reporting
-->
<!--
Thanks for raising a Spring Session issue. Please provide a brief description of your problem along with the version of Spring Session that you are using. If possible, please also consider putting together a sample application that reproduces the issue.
-->

View File

@@ -1,7 +1,3 @@
<!--
For Security Vulnerabilities, please use https://pivotal.io/security#reporting
-->
<!--
Thanks for contributing to Spring Session. Please provide a brief description of your pull-request and reference any related issue numbers (prefix references with #).
-->

1
.gitignore vendored
View File

@@ -12,4 +12,3 @@ out
*.rdb
.checkstyle
!etc/eclipse/.checkstyle
!**/src/**/build

View File

@@ -1,20 +1,19 @@
language: java
sudo: required
services:
- redis-server
services: docker
jdk:
- oraclejdk8
jdk: oraclejdk8
os:
- linux
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
install: true
script: ./gradlew clean build --refresh-dependencies --no-daemon
script: ./gradlew build

179
Jenkinsfile vendored
View File

@@ -1,179 +0,0 @@
properties([
buildDiscarder(logRotator(numToKeepStr: '10')),
pipelineTriggers([
cron('@daily')
]),
])
def SUCCESS = hudson.model.Result.SUCCESS.toString()
currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew clean check --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: check'
throw e
}
finally {
junit '**/build/test-results/*/*.xml'
}
}
}
}
},
jdk9: {
stage('JDK 9') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk9'}"]) {
sh './gradlew clean test --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk9'
throw e
}
}
}
}
},
jdk10: {
stage('JDK 10') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk10'}"]) {
sh './gradlew clean test --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
}
}
}
},
jdk11: {
stage('JDK 11') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
}
},
jdk12: {
stage('JDK 12') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'openjdk12'}"]) {
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk12'
throw e
}
}
}
}
}
if (currentBuild.result == 'SUCCESS') {
parallel artifacts: {
stage('Deploy Artifacts') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew deployArtifacts finalizeDeployArtifacts --no-daemon --refresh-dependencies --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
}
}
}
}
}
}
catch (e) {
currentBuild.result = 'FAILED: artifacts'
throw e
}
}
}
},
docs: {
stage('Deploy Docs') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew deployDocs --no-daemon --refresh-dependencies --stacktrace -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
}
}
}
catch (e) {
currentBuild.result = 'FAILED: docs'
throw e
}
}
}
}
}
}
finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
if (buildNotSuccess || lastBuildNotSuccess) {
stage('Notify') {
node {
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
def details = "The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"
emailext(
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SESSION_TEAM_EMAILS"
)
}
}
}
}

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
https://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.

View File

@@ -1,32 +1,26 @@
image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
image:https://travis-ci.org/spring-projects/spring-session.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-session"]
= Spring Session
Rob Winch
image:https://travis-ci.org/spring-projects/spring-session.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-session"] image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
Spring Session aims to provide a common infrastructure for managing sessions. This provides many benefits including:
Spring Session provides an API and implementations for managing a user's session information, while also making it trivial to support clustered sessions without being tied to an application container specific solution.
It also provides transparent integration with:
* `HttpSession` - allows replacing the `HttpSession` in an application container (i.e. Tomcat) neutral way, with support for providing session IDs in headers to work with RESTful APIs.
* `WebSocket` - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
* `WebSession` - allows replacing the Spring WebFlux's `WebSession` in an application container neutral way.
== Modules
Spring Session consists of the following modules:
* Spring Session Core - provides core Spring Session functionalities and APIs
* Spring Session Data Redis - provides `SessionRepository` and `ReactiveSessionRepository` implementation backed by Redis and configuration support
* Spring Session JDBC - provides `SessionRepository` implementation backed by a relational database and configuration support
* Spring Session Hazelcast - provides `SessionRepository` implementation backed by Hazelcast and configuration support
* Accessing a session from any environment (i.e. web, messaging infrastructure, etc)
* In a web environment
** Support for clustering in a vendor neutral way
** Pluggable strategy for determining the session id
** Easily keep the HttpSession alive when a WebSocket is active
== Code of Conduct
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
== Spring Session Project Site
= Spring Session Project Site
You can find the documentation, issue management, support, samples, and guides for using Spring Session at https://projects.spring.io/spring-session/
== License
= License
Spring Session is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].

View File

@@ -1,40 +1,76 @@
buildscript {
ext {
releaseBuild = version.endsWith('RELEASE')
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.2.0.M2'
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release/' }
maven { url "https://repo.spring.io/plugins-release" }
}
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.25.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE'
classpath 'io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE'
classpath("com.bmuschko:gradle-tomcat-plugin:2.2.5")
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7")
classpath("io.spring.gradle:spring-io-plugin:0.0.4.RELEASE")
classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1')
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
}
}
apply plugin: 'io.spring.convention.root'
apply plugin: 'io.spring.nohttp'
repositories {
mavenCentral()
plugins {
id "org.sonarqube" version "2.1"
}
group = 'org.springframework.session'
description = 'Spring Session'
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = JavaVersion.VERSION_1_8
ext.springBootVersion = '1.4.7.RELEASE'
ext.IDE_GRADLE = "$rootDir/gradle/ide.gradle"
ext.JAVA_GRADLE = "$rootDir/gradle/java.gradle"
ext.SPRING3_GRADLE = "$rootDir/gradle/spring3.gradle"
ext.MAVEN_GRADLE = "$rootDir/gradle/publish-maven.gradle"
ext.BOM_GRADLE = "$rootDir/gradle/bom.gradle"
ext.SAMPLE_GRADLE = "$rootDir/gradle/sample.gradle"
ext.TOMCAT_GRADLE = "$rootDir/gradle/tomcat.gradle"
ext.TOMCAT_6_GRADLE = "$rootDir/gradle/tomcat6.gradle"
ext.TOMCAT_7_GRADLE = "$rootDir/gradle/tomcat7.gradle"
tasks.withType(Test) {
useJUnitPlatform()
ext.releaseBuild = version.endsWith('RELEASE')
ext.snapshotBuild = version.endsWith('SNAPSHOT')
ext.milestoneBuild = !(releaseBuild || snapshotBuild)
apply plugin: 'base'
sonarqube {
properties {
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.projectName", "Spring Session"
property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
property "sonar.links.homepage", 'https://github.com/spring-projects/spring-session'
property "sonar.links.ci", 'https://build.spring.io/browse/SESSION'
property "sonar.links.issue", 'https://github.com/spring-projects/spring-session/issues'
property "sonar.links.scm", 'https://github.com/spring-projects/spring-session'
property "sonar.links.scm_dev", 'https://github.com/spring-projects/spring-session.git'
property "sonar.java.coveragePlugin", "jacoco"
}
}
task configDocsZip(dependsOn: [':docs:asciidoctor',':spring-session:javadoc']) {
doLast {
project.tasks.docsZip.from(project(':docs').asciidoctor) {
into('reference')
}
project.tasks.docsZip.from(project(':spring-session').javadoc) {
into('api')
}
}
}
task docsZip(type: Zip, dependsOn: 'configDocsZip') {
group = "Distribution"
baseName = "spring-session"
classifier = "docs"
description = "Builds -${classifier} archive containing api and reference " +
"for deployment."
}
artifacts {
archives docsZip
}

78
docs/build.gradle Normal file
View File

@@ -0,0 +1,78 @@
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.kordamp.gradle:livereload-gradle-plugin:0.2.1'
}
}
apply plugin: 'org.kordamp.gradle.livereload'
apply from: JAVA_GRADLE
apply plugin: 'org.asciidoctor.convert'
liveReload {
docRoot asciidoctor.sourceDir.canonicalPath
}
repositories {
maven { url 'http://dist.gemstone.com/maven/release' }
}
asciidoctorj {
}
tasks.findByPath("artifactoryPublish")?.enabled = false
dependencies {
testCompile project(':spring-session'),
project(':spring-session-data-mongo'),
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
"org.springframework.data:spring-data-redis:$springDataRedisVersion",
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
"org.springframework:spring-webmvc:${springVersion}",
"org.springframework:spring-websocket:${springVersion}",
"org.springframework:spring-messaging:${springVersion}",
"org.springframework:spring-jdbc:${springVersion}",
"org.springframework.security:spring-security-config:${springSecurityVersion}",
"org.springframework.security:spring-security-web:${springSecurityVersion}",
"org.springframework.security:spring-security-test:${springSecurityVersion}",
"junit:junit:$junitVersion",
"org.mockito:mockito-core:$mockitoVersion",
"org.springframework:spring-test:$springVersion",
"org.assertj:assertj-core:$assertjVersion",
"com.hazelcast:hazelcast:$hazelcastVersion",
"biz.paluch.redis:lettuce:$lettuceVersion",
"javax.servlet:javax.servlet-api:$servletApiVersion"
}
asciidoctor {
def ghTag = snapshotBuild ? 'master' : project.version
def ghUrl = "https://github.com/spring-projects/spring-session/tree/$ghTag/"
attributes 'version-snapshot': snapshotBuild,
'version-milestone': milestoneBuild,
'version-release': releaseBuild,
'gh-url': ghUrl,
'gh-samples-url': "$ghUrl/samples/",
'download-url' : "https://github.com/spring-projects/spring-session/archive/${ghTag}.zip",
'spring-session-version' : version,
'spring-version' : springVersion,
'lettuce-version' : lettuceVersion,
'hazelcast-version' : hazelcastVersion,
'docs-itest-dir' : rootProject.projectDir.path + '/docs/src/integration-test/java/',
'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/',
'docs-test-resources-dir' : rootProject.projectDir.path + '/docs/src/test/resources/',
'samples-dir' : rootProject.projectDir.path + '/samples/',
'session-main-resources-dir' : rootProject.projectDir.path + '/spring-session/src/main/resources/',
'source-highlighter' : 'coderay',
'imagesdir':'./images',
'icons': 'font',
'sectanchors':'',
'idprefix':'',
'idseparator':'-',
'docinfo1':'true',
'revnumber' : project.version
}

View File

@@ -0,0 +1,162 @@
= 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</artifactId>
<version>{spring-session-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</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[tags=class]
----
<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.
[[boot-redis-configuration]]
== Configuring the Redis Connection
Spring Boot automatically creates a `RedisConnectionFactory` that connects Spring Session to a Redis Server on localhost on port 6379 (default port).
In a production environment you need to ensure to update your configuration to point to your Redis server.
For example, you can include the following in your *application.properties*
.src/main/resources/application.properties
----
spring.redis.host=localhost
spring.redis.password=secret
spring.redis.port=6379
----
For more information, refer to https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
[[boot-servlet-configuration]]
== 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.
[[boot-running]]
=== Running the boot Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
====
----
$ ./gradlew :samples:boot:bootRun
----
You should now be able to access the application at http://localhost:8080/
[[boot-explore]]
=== 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.
[[boot-how]]
=== 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 https://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.

View File

@@ -0,0 +1,101 @@
= Spring Session - Custom Cookie
Rob Winch
:toc:
This guide describes how to configure Spring Session to use custom cookies with Java Configuration.
The guide assumes you have already link:./httpsession.html[setup Spring Session in your project].
NOTE: The completed guide can be found in the <<custom-cookie-sample, Custom Cookie sample application>>.
[[custom-cookie-spring-configuration]]
== Spring Java Configuration
Once you have setup Spring Session you can easily customize how the session cookie is written by exposing a `CookieSerializer` as a Spring Bean.
Out of the box, Spring Session comes with `DefaultCookieSerializer`.
Simply exposing the `DefaultCookieSerializer` as a Spring Bean will augment the existing configuration when using configurations like `@EnableRedisHttpSession`.
You can find an example of customizing Spring Session's cookie below:
[source,java]
----
include::{samples-dir}custom-cookie/src/main/java/sample/Config.java[tags=cookie-serializer]
----
<1> We customize the name of the cookie to be JSESSIONID
<2> We customize the path of the cookie to be "/" (rather than the default of the context root)
<3> We customize the domain name pattern (a regular expression) to be `^.+?\\.(\\w+\\.[a-z]+)$`
This allows sharing a session across domains and applications.
If the regular expression does not match, no domain is set and the existing domain will be used.
If the regular expression matches, the first https://docs.oracle.com/javase/tutorial/essential/regex/groups.html[grouping] will be used as the domain.
This means that a request to https://child.example.com will set the domain to example.com.
However, a request to http://localhost:8080/ or https://192.168.1.100:8080/ will leave the cookie unset and thus still work in development without any changes necessary for production.
[WARNING]
====
It is important to note that users should only match on valid domain characters since the domain name is reflected in the response.
This is prevent a malicious user from performing attacks like https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
====
[[custom-cookie-options]]
== Configuration Options
The configuration options available are:
* `cookieName` - the name of the cookie to use
Default "SESSION"
* `useSecureCookie` - specify if a secure cookie be used
Default use value of `HttpServletRequest.isSecure()` at the time of creation.
* `cookiePath` - the path of the cookie
Default is context root
* `cookieMaxAge` - specifies the max age of the cookie to be set at the time the session is created.
Default is -1 which indicates the cookie will be removed when the browser is closed.
* `jvmRoute` - specifies a suffix to be appended to the session id and included in the cookie.
Used to identify which JVM to route to for session affinity.
With some implementations (i.e. Redis) this provides no performance benefit.
However, this can help with tracing logs of a particular user.
* `domainName` - allows specifying a specific domain name to be used for the cookie.
This option is simple to understand, but will likely require a different configuration between development and production environments.
See domainNamePattern as an alternative.
* `domainNamePattern` - a case insensitive pattern used to extract the domain name from the `HttpServletRequest#getServerName()`.
The pattern should provide a single grouping used to extract the value of the cookie domain.
If the regular expression does not match, no domain is set and the existing domain will be used.
If the regular expression matches, the first https://docs.oracle.com/javase/tutorial/essential/regex/groups.html[grouping] will be used as the domain.
[WARNING]
====
It is important to note that users should only match on valid domain characters since the domain name is reflected in the response.
This is prevent a malicious user from performing attacks like https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
====
[[custom-cookie-sample]]
== custom-cookie Sample Application
=== Running the custom-cookie Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
====
----
$ ./gradlew :samples:custom-cookie:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the custom-cookie 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.
If you look at the cookies for the application, you can see the cookie is saved to the custom name of JSESSIONID

View File

@@ -0,0 +1,139 @@
= Spring Session - find by username
Rob Winch
:toc:
This guide describes how to use Spring Session to find sessions by username.
NOTE: The completed guide can be found in the <<findbyusername-sample, findbyusername application>>.
[[findbyusername-assumptions]]
== Assumptions
The guide assumes you have already added Spring Session using the built in Redis configuration support to your application.
The guide also assumes you have already applied Spring Security to your application.
However, we the guide will be somewhat general purpose and can be applied to any technology with minimal changes we will discuss.
[NOTE]
====
If you need to learn how to add Spring Session to your project, please refer to the listing of link:../#samples[samples and guides]
====
== About the Sample
Our sample is using this feature to invalidate the users session that might have been compromised.
Consider the following scenario:
* User goes to library and authenticates to the application
* User goes home and realizes they forgot to log out
* User can log in and terminate the session from the library using clues like the location, created time, last accessed time, etc.
Wouldn't it be nice if we could allow the user to invalidate the session at the library from any device they authenticate with?
This sample demonstrates how this is possible.
[[findbyindexnamesessionrepository]]
== FindByIndexNameSessionRepository
In order to look up a user by their username, you must first choose a `SessionRepository` that implements link:../#api-findbyindexnamesessionrepository[FindByIndexNameSessionRepository].
Our sample application assumes that the Redis support is already setup, so we are ready to go.
== Mapping the username
`FindByIndexNameSessionRepository` can only find a session by the username, if the developer instructs Spring Session what user is associated with the `Session`.
This is done by ensuring that the session attribute with the name `FindByUsernameSessionRepository.PRINCIPAL_NAME_INDEX_NAME` is populated with the username.
Generally, speaking this can be done with the following code immediately after the user authenticates:
[source,java,indent=0]
----
include::{docs-test-dir}docs/FindByIndexNameSessionRepositoryTests.java[tags=set-username]
----
== Mapping the username with Spring Security
Since we are using Spring Security, the user name is automatically indexed for us.
This means we will not have to perform any steps to ensure the user name is indexed.
== Adding Additional Data to Session
It may be nice to associate additional information (i.e. IP Address, the browser, location, etc) to the session.
This makes it easier for the user to know which session they are looking at.
To do this simply determine which session attribute you want to use and what information you wish to provide.
Then create a Java bean that is added as a session attribute.
For example, our sample application includes the location and access type of the session
[source,java,indent=0]
----
include::{samples-dir}findbyusername/src/main/java/sample/session/SessionDetails.java[tags=class]
----
We then inject that information into the session on each HTTP request using a `SessionDetailsFilter`.
For example:
[source,java,indent=0]
----
include::{samples-dir}findbyusername/src/main/java/sample/session/SessionDetailsFilter.java[tags=dofilterinternal]
----
We obtain the information we want and then set the `SessionDetails` as an attribute in the `Session`.
When we retrieve the `Session` by username, we can then use the session to access our `SessionDetails` just like any other session attribute.
[NOTE]
====
You might be wondering at this point why Spring Session does not provide `SessionDetails` functionality out of the box.
The reason, is twofold.
The first is that it is very trivial for applications to implement this themselves.
The second reason is that the information that is populated in the session (and how frequently that information is updated) is highly application dependent.
====
== Finding sessions for a specific user
We can now find all the sessions for a specific user.
[source,java,indent=0]
----
include::{samples-dir}findbyusername/src/main/java/sample/mvc/IndexController.java[tags=findbyusername]
----
In our instance, we find all sessions for the currently logged in user.
However, this could easily be modified for an administrator to use a form to specify which user to look up.
[[findbyusername-sample]]
== findbyusername Sample Application
=== Running the findbyusername Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
====
----
$ ./gradlew :samples:findbyusername: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.
You should also see a listing of active sessions for the currently logged in user.
Let's emulate the flow we discussed in the <<About the Sample>> section
* Open a new incognito window and navigate to http://localhost:8080/
* Enter the following to log in:
** **Username** _user_
** **Password** _password_
* Terminate your original session
* Refresh the original window and see you are logged out

View File

@@ -0,0 +1,132 @@
= Spring Session - Grails
Eric Helgeson
:toc:
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Grails 3.1
NOTE: Grails 3.1 is based off spring boot 1.3 so much of the advanced configuration and options can be found in the boot docs as well.
NOTE: The completed guide can be found in the <<grails3-sample, Grails 3 sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure to update your dependencies.
We assume you are working with a working Grails 3.1 web profile.
Add the following dependencies:
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
----
dependencies {
compile 'org.springframework.boot:spring-boot-starter-redis'
compile 'org.springframework.session:spring-session:{spring-session-version}'
}
----
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:
.build.gradle
[source,groovy]
----
repositories {
maven {
url 'https://repo.spring.io/libs-snapshot'
}
}
----
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:
.build.gradle
[source,groovy]
----
repositories {
maven {
url 'https://repo.spring.io/libs-milestone'
}
}
----
endif::[]
[[grails3-redis-configuration]]
== Configuring the Redis Connection
Spring Boot automatically creates a `RedisConnectionFactory` that connects Spring Session to a Redis Server on localhost on port 6379 (default port).
In a production environment you need to ensure to update your configuration to point to your Redis server.
For example, you can include the following in your *application.yml*
.grails-app/conf/application.yml
[source,yml]
----
spring:
redis:
host: localhost
password: secret
port: 6397
----
For more information, refer to https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
[[grails3-sample]]
== Grails 3 Sample Application
The Grails 3 Sample Application demonstrates how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Grails.
[[grails3-running]]
=== Running the Grails 3 Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
====
----
$ ./gradlew :samples:grails3:bootRun
----
You should now be able to access the application at http://localhost:8080/test/index
[[grails3-explore]]
=== 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.
[[grails3-how]]
=== 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 https://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/test/index and observe that we are no longer authenticated.
NOTE: Spring Session will not work with grails flash scope without additional work. +
See this answer for an explanation: https://stackoverflow.com/a/43311427

View File

@@ -0,0 +1,195 @@
= Spring Session and Spring Security with Hazelcast
Tommy Ludwig; Rob Winch
:toc:
This guide describes how to use Spring Session along with Spring Security using Hazelcast as your data store.
It assumes you have already applied Spring Security to your application.
NOTE: The completed guide can be found in the <<hazelcast-spring-security-sample, Hazelcast Spring 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>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>{hazelcast-version}</version>
</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::[]
// tag::config[]
[[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::{docs-test-dir}docs/http/HazelcastHttpSessionConfig.java[tags=config]
----
<1> The `@EnableHazelcastHttpSession` 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 Hazelcast.
<2> In order to support retrieval of sessions by principal name index, appropriate `ValueExtractor` needs to be registered.
Spring Session provides `PrincipalNameExtractor` for this purpose.
<3> We create a `HazelcastInstance` that connects Spring Session to Hazelcast.
By default, an embedded instance of Hazelcast is started and connected to by the application.
For more information on configuring Hazelcast, refer to the https://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-configuration[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 `SessionConfig` class.
Since our application is already loading Spring configuration using our `SecurityInitializer` class, we can simply add our `SessionConfig` class to it.
.src/main/java/sample/SecurityInitializer.java
[source,java]
----
include::{samples-dir}hazelcast-spring/src/main/java/sample/SecurityInitializer.java[tags=class]
----
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}hazelcast-spring/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`.
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`.
// end::config[]
[[hazelcast-spring-security-sample]]
== Hazelcast Spring Security Sample Application
=== Running the Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
Hazelcast will run in embedded mode with your application by default, but if you want to connect
to a stand alone instance instead, you can configure it by following the instructions in the
https://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-configuration[reference documentation].
====
----
$ ./gradlew :samples:hazelcast-spring: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 Hazelcast rather than Tomcat's `HttpSession` implementation.
=== How does it work?
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Hazelcast.
Spring Session replaces the `HttpSession` with an implementation that is backed by a `Map` in Hazelcast.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Hazelcast.
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]).
=== Interact with the data store
If you like, you can remove the session using https://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-java-client[a Java client],
https://docs.hazelcast.org/docs/latest/manual/html-single/index.html#other-client-implementations[one of the other clients], or the
https://docs.hazelcast.org/docs/latest/manual/html-single/index.html#management-center[management center].
==== Using the console
For example, using the management center console after connecting to your Hazelcast node:
default> ns spring:session:sessions
spring:session:sessions> m.clear
TIP: The Hazelcast documentation has instructions for https://docs.hazelcast.org/docs/latest/manual/html-single/index.html#executing-console-commands[the console].
Alternatively, you can also delete the explicit key. Enter the following into the console ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
spring:session:sessions> m.remove 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
==== Using the REST API
As described in the other clients section of the documentation, there is a
https://docs.hazelcast.org/docs/latest/manual/html-single/index.html#rest-client[REST API]
provided by the Hazelcast node(s).
For example, you could delete an individual key as follows (replacing `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie):
$ curl -v -X DELETE http://localhost:xxxxx/hazelcast/rest/maps/spring:session:sessions/7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
TIP: The port number of the Hazelcast node will be printed to the console on startup. Replace `xxxxx` above with the port number.
Now observe that you are no longer authenticated with this session.

View File

@@ -0,0 +1,270 @@
= Spring Session - HttpSession with GemFire Client/Server using Spring Boot
John Blum
:toc:
This guide describes how to build a _Spring Boot_ application configured with _Spring Session_ to transparently leverage
Pivotal GemFire to back a web application's `HttpSession`.
In this sample, GemFire's client/server topology is employed using a pair of _Spring Boot_ applications, one to
configure and run a GemFire Server and another to configure and run the client, Spring MVC-based web application
making use of the `HttpSession`.
NOTE: The completed guide can be found in the <<httpsession-gemfire-boot-sample,HttpSession with GemFire using Spring Boot Sample Application>>.
== Updating Dependencies
Before using _Spring Session_, you must ensure that the required dependencies are included.
If you are using Maven, include the following `dependencies` in your _pom.xml_:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-gemfire</artifactId>
<version>{spring-session-version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
----
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
If you are using Maven, include the following `repository` declaration 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 add the Spring Milestone Maven Repository.
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
----
endif::[]
// tag::config[]
[[httpsession-spring-java-configuration]]
== Spring Boot Configuration
After adding the required dependencies and repository declarations, we can create our Spring configuration
for both the GemFire client and server using _Spring Boot_. The Spring configuration is responsible for
creating a Servlet Filter that replaces the `HttpSession` with an implementation backed by _Spring Session_
and GemFire.
=== Spring Boot-based GemFire Server
We start with the _Spring Boot_ application for configuring and bootstrapping a GemFire Server process...
[source,java]
----
include::{samples-dir}httpsession-gemfire-boot/src/main/java/sample/server/GemFireServer.java[tags=class]
----
<1> The `@EnableGemFireHttpSession` annotation is used on the GemFire Server to mainly define the corresponding
Region (e.g. `ClusteredSpringSessions`, the default) in which Session state information will be stored
and managed by GemFire. As well, we have specified an arbitrary expiration attribute (i.e. `maxInactiveIntervalInSeconds`)
for when the Session will timeout, which is triggered by a GemFire Region entry expiration event that also invalidates
the Session object in the Region.
<2> Next, we define a few `Properties` that allow us to configure certain aspects of the GemFire Server using
https://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html[GemFire's System properties].
<3> Then, we create an instance of the GemFire `Cache` using our defined `Properties`.
<4> Finally, we configure and start a `CacheServer` running in the GemFire Server to listen for connections
from cache clients. The `CacheServer's` `Socket` will be used to connect our GemFire cache client,
_Spring Boot_ web application to the server.
The sample also makes use of a `PropertySourcesPlaceholderConfigurer` bean in order to externalize the sample application
configuration to affect GemFire and application configuration/behavior from the command-line (e.g. such as GemFire's
`log-level` using the `gemfire.log.level` System property; more details below).
=== Spring Boot-based GemFire cache client Web application
Now, we create our _Spring Boot_ Web application exposing our Web service with Spring MVC, running as a
GemFire cache client connected to our _Spring Boot_-based GemFire Server, using Spring Session backed by GemFire
to manage Session state in a clustered, replicated fashion.
[source,java]
----
include::{samples-dir}httpsession-gemfire-boot/src/main/java/sample/client/Application.java[tags=class]
----
<1> Here, again, we use the `@EnableGemFireHttpSession` annotation to not only configure the GemFire cache client,
but to also override the (HTTP) Web application container's `HttpSession` and replace it with a Session implementation
backed by _Spring Session_ and GemFire. Also notice, we did not define any Session expiration timeout with the
`maxInactiveIntervalInSeconds` attribute this time. That is because the Session expiration is managed by GemFire,
on the server, which will appropriately notify the cache client when the Session times out. Again, we have just
resorted to using the default named Region, `ClusteredSpringSessions`. Of course, we can change the Region name,
but we must do so on both the client and the server. That is a GemFire requirement, not a
_Spring Session Data GemFire_ requirement.
<2> Similarly to the server configuration, we set a few basic GemFire System `Properties` on the client.
<3> Although, this time, an instance of `ClientCache` is created with the `ClientCacheFactoryBean`
from _Spring Data GemFire_.
<4> However, in order to connect to the GemFire Server we must define a GemFire `Pool` bean containing a
pool of connections to the server. Whenever a client Region entry operation corresponding to a Session update occurs,
the client-side Region will use an existing, pooled connection to route the operation to the server.
<5> The following _Spring_ `BeanPostProcessor` (along with some utility methods) are only needed for testing purposes
and are not required by any production code. Specifically, the `BeanPostProcessor` along with the code referenced in *6*
is useful in integration test cases where the client and server processes are forked by the test framework. It is pretty
easy to figure out that a race condition is imminent without proper coordination between the client and the server,
therefore, the BPP and `ClientMembershipListener` help sync the interaction between the client and the server
on startup during automated testing.
<6> Navigates the Web application to the home page (`index.html`), which uses **Thymeleaf** templates for server-side
pages.
<7> Heartbeat Web service endpoint (useful for manual testing purposes).
<8> Web service endpoint allowing the user to add a Session attribute using the Web application UI. In addition,
the webapp stores an additional Session attribute (`requestCount`) to keep track of how many HTTP requests the user
has sent during the current "session".
There are many other utility methods, so please refer to the actual source code for full details.
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds or thousands of GemFire data nodes
(servers), it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator
passes meta-data to clients about the servers available, their load and which servers have the client's data of interest,
which is particularly important in direct, single-hop data access and latency-sensitive operations. See more details
about the https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
NOTE: For more information on configuring _Spring Data GemFire_, refer to the https://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of both _Spring Session_
and GemFire out-of-the-box using the following attributes:
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
* `clientRegionShort` - specifies GemFire's https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
with a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`). This attribute is only used when configuring client Region.
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
* `serverRegionShort` - specifies GemFire's https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
using a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
NOTE: It is important to remember that the GemFire client Region name must match a server Region by the same name if
the client Region is a `PROXY` or `CACHING_PROXY`. Client and server Region names are not required to match if
the client Region used to store Spring Sessions is `LOCAL`. However, keep in mind that your session state will not
be propagated to the server and you lose all the benefits of using GemFire to store and manage distributed, replicated
session state information in a cluster.
[[httpsession-gemfire-boot-sample]]
== HttpSession with GemFire using Spring Boot Sample Application
=== Running the httpsession-gemfire-boot Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
First, you must run the server:
----
$ ./gradlew :samples:httpsession-gemfire-boot:run [-Dgemfire.log-level=config]
----
Then, in a separate terminal, run the client:
----
$ ./gradlew :samples:httpsession-gemfire-boot:bootRun [-Dgemfire.log-level=config]
----
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
is the client cache and the server is standalone.
=== Exploring the httpsession-gemfire-boot Sample Application
Try using the application. Fill out the form with the following information:
* **Attribute Name:** _username_
* **Attribute Value:** _test_
Now click the **Set Attribute** button. You should now see the attribute name and value displayed in the table
along with an additional attribute (`requestCount`) indicating the number of Session interactions (via HTTP requests).
=== How does it work?
We interact with the standard `HttpSession` in the the Spring MVC web service endpoint, shown here for convenience:
.src/main/java/sample/SessionServlet.java
[source,java]
----
@RequestMapping(method = RequestMethod.POST, path = "/session")
public String session(HttpSession session, ModelMap modelMap,
@RequestParam(name = "attributeName", required = false) String name,
@RequestParam(name = "attributeValue", required = false) String value) {
modelMap.addAttribute("sessionAttributes",
attributes(setAttribute(updateRequestCount(session), name, value)));
return INDEX_TEMPLATE_VIEW_NAME;
}
----
Instead of using the embedded HTTP server's `HttpSession`, we are actually persisting the Session state in GemFire.
_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]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see https://gemfire.docs.pivotal.io/docs-gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
NOTE: In order to run the following, you must uncomment the lines in the `GemFireServer` class, `gemfireProperties` bean
for the following GemFire System properties: `jmx-manager` and `jmx-manager-start`.
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:
$ gfsh
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
....
gfsh>connect --jmx-manager=localhost[1099]
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
Result : true
startCount : 0
endCount : 20
Rows : 1
Result
------------------------------------
70002719-3c54-4c20-82c3-e7faa6b718f3
NEXT_STEP_NAME : END
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
....
NOTE: The _GemFire User Guide_ has more detailed instructions on using https://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
Alternatively, you can wait **20 seconds** for the session to expire and timeout, and then refresh the page. The attribute
we added should no longer be displayed in the table.

View File

@@ -0,0 +1,273 @@
= Spring Session - HttpSession with GemFire Client/Server using XML (Quick Start)
John Blum
:toc:
This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's
`HttpSession` using XML Configuration.
NOTE: The completed guide can be found in the <<httpsession-gemfire-clientserver-xml-sample-app,HttpSession with GemFire (Client/Server) using XML Sample Application>>.
== Updating Dependencies
Before using Spring Session, you must ensure that the required dependencies are included.
If you are using Maven, include the following `dependencies` in your _pom.xml_:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-gemfire</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 add the Spring Snapshot Maven Repository.
If you are using Maven, include the following `repository` declaration 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 add the Spring Milestone Maven Repository.
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
----
endif::[]
// tag::config[]
[[httpsession-spring-xml-configuration]]
== Spring XML Configuration
After adding the required dependencies and repository declarations, we can create our Spring configuration.
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
with an implementation backed by Spring Session and GemFire.
Add the following Spring Configuration:
[source,xml]
----
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/spring/session-client.xml[tags=beans]
----
<1> Spring annotation configuration support is enabled with `<context:annotation-config/>` element so that any
Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported
by Spring will be configured appropriately.
<2> The `META-INF/spring/application.properties` file are used along with the `PropertySourcesPlaceholderConfigurer`
bean to replace placeholders in the Spring XML configuration meta-data with the approrpriate property values.
<3> Then the `GemFireCacheSeverReadyBeanPostProcessor`is registered to determine whether a GemFire Server
at the designated host/port is running and listening for client connections, blocking client startup until
the server is available and ready.
<4> Next, we include a `Properties` bean to configure certain aspects of the GemFire client cache using
https://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html[GemFire's System Properties].
In this case, we are just setting GemFire's `log-level` from a application-specific System property, defaulting
to `warning` if unspecified.
<5> Then we create a instance of a GemFire `ClientCache` initialized with our `gemfireProperties`.
<6> We configure a Pool of client connections to talk to the GemFire Server in our Client/Server topology.
In our configuration, we use sensible settings for timeouts, number of connections and so on. Also, our `Pool`
has been configured to connect directly to a server.
<7> Finally, the `GemFireHttpSessionConfiguration` is registered to enable Spring Session functionality.
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
to clients about the servers available, load and which servers have the client's data of interest, which is particularly
important for single-hop, direct data access. See more details about the https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
NOTE: For more information on configuring _Spring Data GemFire_, refer to the https://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
=== Server Configuration
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
session state information to the server to manage.
In this sample, we will use the following GemFire Server Java Configuration:
[source,xml]
----
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/session-server.xml[tags=beans]
----
<1> First, we enable Spring annotation config support with the `<context:annotation-config>` element so that any
Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported
by Spring will be configured appropriately.
<2> A `PropertySourcesPlaceholderConfigurer` is registered to replace placeholders in our Spring XML configuration
meta-data with property values in the `META-INF/spring/application.properties` file.
<3> Next, we configure the GemFire Server using GemFire System Properties very much like our P2P samples.
With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a
JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties.
<4> Then we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
<5> We also setup a GemFire `CacheServer` instance running on *localhost*, listening to port **11235**,
ready to accept our client connection.
<6> Finally, we enable the same Spring Session functionality we used on the client by registering an instance of
`GemFireHttpSessionConfiguration`, except that we set the session expiration timeout to **30 seconds**.
We will explain later what this means.
The GemFire Server configuration gets bootstrapped with the following:
[source,java]
----
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/java/sample/Application.java[tags=class]
----
TIP: Instead of a simple Java class with a main method, you could also use _Spring Boot_.
<1> The `@Configuration` annotation designates this Java class as a source for Spring configuration meta-data using
Spring's annotation configuration support.
<2> Primarily, the configuration comes from the `META-INF/spring/session-server.xml` file, which is also the reason
why _Spring Boot_ was not used in this sample, since using XML seemingly defeats the purpose and benefits
of using Spring Boot. However, this sample is about demonstrating how to use Spring XML to configure
the GemFire client and server.
== XML Servlet Container Initialization
Our <<httpsession-spring-xml-configuration,Spring XML 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 and GemFire.
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session-client.xml` configuration file.
We do this with the following configuration:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
----
The https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener]
reads the `contextConfigLocation` context parameter value and picks up our _session-client.xml_ configuration file.
Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter`
for every request.
The following snippet performs this last step for us:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
----
The https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy]
will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy`
is invoked, the `springSessionRepositoryFilter` will be invoked.
// end::config[]
[[httpsession-gemfire-clientserver-xml-sample-app]]
== HttpSession with GemFire (Client/Server) using XML Sample Application
=== Running the httpsession-gemfire-clientserver-xml Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
First, you need to run the server using:
----
$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:run [-Dsample.httpsession.gemfire.log-level=info]
----
Now, in a separate terminal, you can run the client using:
----
$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
----
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
is the client cache and the server is standalone.
=== Exploring the httpsession-gemfire-clientserver-xml Sample Application
Try using the application. Fill out the form with the following information:
* **Attribute Name:** _username_
* **Attribute Value:** _john_
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-gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class]
----
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
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]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:
$ gfsh
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
....
gfsh>connect --jmx-manager=localhost[1099]
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
Result : true
startCount : 0
endCount : 20
Rows : 1
Result
------------------------------------
70002719-3c54-4c20-82c3-e7faa6b718f3
NEXT_STEP_NAME : END
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
....
NOTE: The _GemFire User Guide_ has more detailed instructions on using https://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
Alternatively, you can wait *30 seconds* for the session to timeout (i.e. expire) and refresh the page. Again, the
attribute we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page,
you will inadvertently create a new (empty) session. If you run the query again, you will also see two session IDs,
the new and the old, since GemFire keeps a "tombstone" of the old session around.

View File

@@ -0,0 +1,272 @@
= Spring Session - HttpSession with GemFire Client/Server (Quick Start)
John Blum
:toc:
This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's
`HttpSession` using Java Configuration.
NOTE: The completed guide can be found in the <<httpsession-gemfire-clientserver-java-sample-app,HttpSession with GemFire (Client/Server) Sample Application>>.
== Updating Dependencies
Before using Spring Session, you must ensure that the required dependencies are included.
If you are using Maven, include the following `dependencies` in your _pom.xml_:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-gemfire</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 add the Spring Snapshot Maven Repository.
If you are using Maven, include the following `repository` declaration 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 add the Spring Milestone Maven Repository.
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
----
endif::[]
// tag::config[]
[[httpsession-spring-java-configuration]]
== Spring Java Configuration
After adding the required dependencies and repository declarations, we can create our Spring configuration.
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
with an implementation backed by Spring Session and GemFire.
Add the following Spring Configuration:
[source,java]
----
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/ClientConfig.java[tags=class]
----
<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session
and GemFire.
<2> Next, we register a `Properties` bean that allows us to configure certain aspects of the GemFire client cache
using https://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html[GemFire's System properties].
<3> We use the `Properties` to configure an instance of a GemFire `ClientCache`.
<4> Then, we configure a `Pool` of client connections to talk to the GemFire Server in our Client/Server topology. In our
configuration, we have used sensible settings for timeouts, number of connections and so on. Also, the `Pool` has been
configured to connect directly to a server. Learn more about various `Pool` configuration settings from the
https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API].
<56> Finally, we include a Spring `BeanPostProcessor` to block the client until our GemFire Server is up and running,
listening for and accepting client connections.
The `gemfireCacheServerReadyBeanPostProcessor` is necessary in order to coordinate the client and server in
an automated fashion during testing, but unnecessary in situations where the GemFire cluster is already presently
running, such as in production.
The `BeanPostProcessor` uses a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/management/membership/ClientMembershipListener.html[ClientMembershipListener]
that will be notified when the client has successfully connected to the server. Once a connection has been established,
the listener releases the latch that the `BeanPostProcessor` will wait on (up to the specified timeout) in the
`postProcessAfterInitialization` callback to block the client.
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
to clients about the servers available, load and which servers have the client's data of interest, which is particularly
important for single-hop, direct data access. See more details about the https://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
NOTE: For more information on configuring _Spring Data GemFire_, refer to the https://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of both Spring Session
and GemFire out-of-the-box using the following attributes:
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
* `clientRegionShort` - specifies GemFire's https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
with a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`). This attribute is only used when configuring client Region.
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
* `serverRegionShort` - specifies GemFire's https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
using a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
NOTE: It is important to note that the GemFire client Region name must match a server Region by the same name if
the client Region is a `PROXY` or `CACHING_PROXY`. Names are not required to match if the client Region used to
store Spring Sessions is `LOCAL`, however, keep in mind that your session state will not be propagated to the server
and you lose all benefits of using GemFire to store and manage distributed, replicated session state information
in a cluster.
NOTE: `serverRegionShort` is ignored in a client/server cache configuration and only applies when
a peer-to-peer (P2P) topology, and more specifically, a GemFire peer cache is used.
=== Server Configuration
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
session state to the server to manage.
In this sample, we will use the following GemFire Server Java Configuration:
[source,java]
----
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/ServerConfig.java[tags=class]
----
<1> On the server, we also configure Spring Session using the `@EnableGemFireHttpSession` annotation. This ensures
the Region names on both the client and server match (in this sample, we use the default "_ClusteredSpringSessions_").
We have also set the session timeout to **30 seconds**. Later, we will see how this timeout is used.
<2> Next, we configure the GemFire Server using GemFire System Properties very much like our P2P samples.
With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a
JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties.
<3> Then, we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
<4> We also setup a GemFire `CacheServer` instance running on **localhost**, listening to port **12480**,
ready to accept our client connection.
<5> Finally, we declare a `main` method as an entry point for launching and running our GemFire Server
from the command-line.
== Java Servlet Container Initialization
Our <<httpsession-spring-java-configuration,Spring Java Configuration>> created a Spring bean named `springSessionRepositoryFilter`
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession`
with a custom implementation backed by Spring Session and GemFire.
In order for our `Filter` to do its magic, Spring needs to load our `ClientConfig` class. We also need to ensure our
Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session
provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy.
You can find an example below:
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}httpsession-gemfire-clientserver/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`.
This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container
and used for every request.
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `ClientConfig`.
// end::config[]
[[httpsession-gemfire-clientserver-java-sample-app]]
== HttpSession with GemFire (Client/Server) Sample Application
=== Running the httpsession-gemfire-clientserver Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
First, you need to run the server using:
----
$ ./gradlew :samples:httpsession-gemfire-clientserver:run [-Dsample.httpsession.gemfire.log-level=info]
----
Then, in a separate terminal, you run the client using:
----
$ ./gradlew :samples:httpsession-gemfire-clientserver:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
----
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
is the client cache and the server is standalone.
=== Exploring the httpsession-gemfire-clientserver Sample Application
Try using the application. Fill out the form with the following information:
* **Attribute Name:** _username_
* **Attribute Value:** _john_
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-gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class]
----
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
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]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:
$ gfsh
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
....
gfsh>connect --jmx-manager=localhost[1099]
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
Result : true
startCount : 0
endCount : 20
Rows : 1
Result
------------------------------------
70002719-3c54-4c20-82c3-e7faa6b718f3
NEXT_STEP_NAME : END
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
....
NOTE: The _GemFire User Guide_ has more detailed instructions on using https://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
Alternatively, you can wait **30 seconds** for the session to expire and timeout, and then refresh the page. The attribute
we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page, you will inadvertently
create a new (empty) session. If you run the query again, you will also see two session IDs, the new and the old,
since GemFire keeps a "tombstone" of the old session around.

View File

@@ -0,0 +1,210 @@
= Spring Session - HttpSession with GemFire P2P using XML (Quick Start)
John Blum, Rob Winch
:toc:
This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back
a web application's `HttpSession` using XML Configuration.
NOTE: The completed guide can be found in the <<httpsession-gemfire-p2p-xml-sample-app,HttpSession with GemFire (P2P) using XML Sample Application>>.
== Updating Dependencies
Before using Spring Session, you must ensure that the required dependencies are included.
If you are using Maven, include the following `dependencies` in your _pom.xml_:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-gemfire</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 add the Spring Snapshot Maven Repository.
If you are using Maven, include the following `repository` declaration 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 add the Spring Milestone Maven Repository.
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
----
endif::[]
// tag::config[]
[[httpsession-spring-xml-configuration]]
== Spring XML Configuration
After adding the required dependencies and repository declarations, we can create our Spring configuration.
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
with an implementation backed by Spring Session and GemFire.
Add the following Spring Configuration:
.src/main/webapp/WEB-INF/spring/session.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
----
<1> We use the combination of `<context:annotation-config/>` and `GemFireHttpSessionConfiguration` because Spring Session
does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
This creates a Spring bean with the name of `springSessionRepositoryFilter` that implements `Filter`. The filter is what
replaces the `HttpSession` with an implementation backed by Spring Session.
In this instance, Spring Session is backed by GemFire.
<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node
a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node
will be a standalone server. GemFire's `log-level` is set using an application-specific System property
(`sample.httpsession.gemfire.log-level`) that a user can specify on the command-line when running this application
using either Maven or Gradle (default is "_warning_").
<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running
Spring Session sample application.
TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific
JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node.
NOTE: For more information on configuring _Spring Data GemFire_, refer to the https://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
== XML Servlet Container Initialization
Our <<httpsession-spring-xml-configuration,Spring XML 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 and GemFire.
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration file.
We do this with the following configuration:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
----
The https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener]
reads the `contextConfigLocation` context parameter value and picks up our _session.xml_ configuration file.
Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter`
for every request.
The following snippet performs this last step for us:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
----
The https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy]
will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy`
is invoked, the `springSessionRepositoryFilter` will be invoked.
// end::config[]
[[httpsession-gemfire-p2p-xml-sample-app]]
== HttpSession with GemFire (P2P) using XML Sample Application
=== Running the httpsession-gemfire-p2p-xml Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
----
$ ./gradlew :samples:httpsession-gemfire-p2p-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the httpsession-gemfire-p2p-xml Sample Application
Try using the application. Fill out the form with the following information:
* **Attribute Name:** _username_
* **Attribute Value:** _john_
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-gemfire-p2p-xml/src/main/java/sample/SessionServlet.java[tags=class]
----
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
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]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:
$ gfsh
Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
....
gfsh>connect --jmx-manager=localhost[1099]
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
Result : true
startCount : 0
endCount : 20
Rows : 1
Result
------------------------------------
70002719-3c54-4c20-82c3-e7faa6b718f3
NEXT_STEP_NAME : END
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
....
NOTE: The _GemFire User Guide_ has more detailed instructions on using https://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.

View File

@@ -0,0 +1,209 @@
= Spring Session - HttpSession with GemFire P2P (Quick Start)
John Blum, Rob Winch
:toc:
This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back
a web application's `HttpSession` using Java Configuration.
NOTE: The completed guide can be found in the <<httpsession-gemfire-p2p-java-sample-app,HttpSession with GemFire (P2P) Sample Application>>.
== Updating Dependencies
Before using Spring Session, you must ensure that the required dependencies are included.
If you are using Maven, include the following `dependencies` in your _pom.xml_:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-gemfire</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 add the Spring Snapshot Maven Repository.
If you are using Maven, include the following `repository` declaration 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 add the Spring Milestone Maven Repository.
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
----
endif::[]
// tag::config[]
[[httpsession-spring-java-configuration]]
== Spring Java Configuration
After adding the required dependencies and repository declarations, we can create our Spring configuration.
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
with an implementation backed by Spring Session and GemFire.
Add the following Spring Configuration:
[source,java]
----
include::{samples-dir}httpsession-gemfire-p2p/src/main/java/sample/Config.java[tags=class]
----
<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session.
In this instance, Spring Session is backed by GemFire.
<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node
a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node
will be a standalone server. GemFire's `log-level` is set using an application-specific System property (`sample.httpsession.gemfire.log-level`)
that a user can specify on the command-line when running this sample application using either Maven or Gradle (default is "_warning_").
<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running
Spring Session sample application.
TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific
JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node.
NOTE: For more information on configuring _Spring Data GemFire_, refer to the https://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of Spring Session
and GemFire out-of-the-box using the following attributes:
* `maxInactiveIntervalInSeconds` - controls HttpSession idle-timeout expiration (defaults to **30 minutes**).
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "_ClusteredSpringSessions_").
* `serverRegionShort` - specifies GemFire https://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies]
with a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
(default is `PARTITION`).
NOTE: `clientRegionShort` is ignored in a peer cache configuration and only applies when a client-server topology,
and more specifically, a GemFire client cache is used.
== Java Servlet Container Initialization
Our <<httpsession-spring-java-configuration,Spring Java Configuration>> created a Spring bean named `springSessionRepositoryFilter`
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession`
with a custom implementation backed by Spring Session and GemFire.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class. We also need to ensure our
Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session
provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy.
You can find an example below:
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}httpsession-gemfire-p2p/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`.
This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container
and used for every request.
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `Config`.
// end::config[]
[[httpsession-gemfire-p2p-java-sample-app]]
== HttpSession with GemFire (P2P) Sample Application
=== Running the httpsession-gemfire-p2p Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
----
$ ./gradlew :samples:httpsession-gemfire-p2p:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the httpsession-gemfire-p2p Sample Application
Try using the application. Fill out the form with the following information:
* **Attribute Name:** _username_
* **Attribute Value:** _john_
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-gemfire-p2p/src/main/java/sample/SessionServlet.java[tags=class]
----
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
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]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:
$ gfsh
Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
....
gfsh>connect --jmx-manager=localhost[1099]
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
Result : true
startCount : 0
endCount : 20
Rows : 1
Result
------------------------------------
70002719-3c54-4c20-82c3-e7faa6b718f3
NEXT_STEP_NAME : END
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
....
NOTE: The _GemFire User Guide_ has more detailed instructions on using https://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.

View File

@@ -0,0 +1,152 @@
= Spring Session - Spring Boot
Rob Winch, Vedran Pavić
:toc:
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` when using Spring Boot.
NOTE: The completed guide can be found in the <<httpsession-jdbc-boot-sample, httpsession-jdbc-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-jdbc</artifactId>
<version>{spring-session-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</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::[]
// tag::config[]
[[httpsession-jdbc-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}httpsession-jdbc-boot/src/main/java/sample/config/HttpSessionConfig.java[tags=class]
----
<1> The `@EnableJdbcHttpSession` 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 a relational database.
[[httpsession-jdbc-boot-configuration]]
== Configuring the DataSource
Spring Boot automatically creates a `DataSource` that connects Spring Session to an embedded instance of H2 database.
In a production environment you need to ensure to update your configuration to point to your relational database.
For example, you can include the following in your *application.properties*
.src/main/resources/application.properties
----
spring.datasource.url=jdbc:postgresql://localhost:5432/myapp
spring.datasource.username=myapp
spring.datasource.password=secret
----
For more information, refer to https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-configure-datasource[Configure a DataSource] portion of the Spring Boot documentation.
[[httpsession-jdbc-boot-servlet-configuration]]
== Servlet Container Initialization
Our <<httpsession-jdbc-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.
// end::config[]
[[httpsession-jdbc-boot-sample]]
== httpsession-jdbc-boot Sample Application
The httpsession-jdbc-boot Sample Application demonstrates how to use Spring Session to transparently leverage H2 database to back a web application's `HttpSession` when using Spring Boot.
[[httpsession-jdbc-boot-running]]
=== Running the httpsession-jdbc-boot Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
----
$ ./gradlew :samples:httpsession-jdbc-boot:bootRun
----
You should now be able to access the application at http://localhost:8080/
[[httpsession-jdbc-boot-explore]]
=== 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 H2 database rather than Tomcat's `HttpSession` implementation.
[[httpsession-jdbc-boot-how]]
=== How does it work?
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
Spring Session replaces the `HttpSession` with an implementation that is backed by a relational database.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into H2 database.
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 H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL)
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

View File

@@ -0,0 +1,162 @@
= Spring Session - HttpSession (Quick Start)
Rob Winch, Vedran Pavić
:toc:
This guide describes how to use Spring Session to transparently leverage a relational to back a web application's `HttpSession` with XML based configuration.
NOTE: The completed guide can be found in the <<httpsession-jdbc-xml-sample, httpsession-jdbc-xml 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-jdbc</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::[]
// tag::config[]
[[httpsession-jdbc-xml-spring-configuration]]
== Spring XML 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:
.src/main/webapp/WEB-INF/spring/session.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-jdbc-xml/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
----
<1> We use the combination of `<context:annotation-config/>` and `JdbcHttpSessionConfiguration` because Spring Session does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
This 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 a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of H2 database.
We configure the H2 database to create database tables using the SQL script which is included in Spring Session.
<3> We create a `transactionManager` that manages transactions for previously configured `dataSource`.
For additional information on how to configure data access related concerns, please refer to the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html[Spring Framework Reference Documentation].
== XML Servlet Container Initialization
Our <<httpsession-xml-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, we need to instruct Spring to load our `session.xml` configuration.
We do this with the following configuration:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
----
The https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener] reads the contextConfigLocation and picks up our session.xml configuration.
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
The following snippet performs this last step for us:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
----
The https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] will look up a Bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`.
For every request that `DelegatingFilterProxy` is invoked, the `springSessionRepositoryFilter` will be invoked.
// end::config[]
[[httpsession-jdbc-xml-sample]]
== httpsession-jdbc-xml Sample Application
=== Running the httpsession-jdbc-xml Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
----
$ ./gradlew :samples:httpsession-jdbc-xml:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the httpsession-jdbc-xml 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-jdbc-xml/src/main/java/sample/SessionServlet.java[tags=class]
----
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
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 H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL)
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.

View File

@@ -0,0 +1,152 @@
= Spring Session - HttpSession (Quick Start)
Rob Winch, Vedran Pavić
:toc:
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` with Java Configuration.
NOTE: The completed guide can be found in the <<httpsession-jdbc-sample, httpsession-jdbc 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-jdbc</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::[]
// tag::config[]
[[httpsession-jdbc-spring-configuration]]
== Spring Java 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-jdbc/src/main/java/sample/Config.java[tags=class]
----
<1> The `@EnableJdbcHttpSession` 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 a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of H2 database.
We configure the H2 database to create database tables using the SQL script which is included in Spring Session.
<3> We create a `transactionManager` that manages transactions for previously configured `dataSource`.
For additional information on how to configure data access related concerns, please refer to the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html[Spring Framework Reference Documentation].
== Java 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[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`.
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-jdbc-sample]]
== httpsession-jdbc Sample Application
=== Running the httpsession-jdbc Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
----
$ ./gradlew :samples:httpsession-jdbc:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the httpsession-jdbc 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-jdbc/src/main/java/sample/SessionServlet.java[tags=class]
----
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
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 H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL)
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.

View File

@@ -0,0 +1,179 @@
= 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` with XML based configuration.
NOTE: The completed guide can be found in the <<httpsession-xml-sample, httpsession-xml 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>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>{lettuce-version}</version>
</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::[]
// tag::config[]
[[httpsession-xml-spring-configuration]]
== Spring XML 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:
.src/main/webapp/WEB-INF/spring/session.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
----
<1> We use the combination of `<context:annotation-config/>` and `RedisHttpSessionConfiguration` because Spring Session does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
This 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.
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, refer to the https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
== XML Servlet Container Initialization
Our <<httpsession-xml-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, we need to instruct Spring to load our `session.xml` configuration.
We do this with the following configuration:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
----
The https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener] reads the contextConfigLocation and picks up our session.xml configuration.
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
The following snippet performs this last step for us:
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
----
The https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] will look up a Bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`.
For every request that `DelegatingFilterProxy` is invoked, the `springSessionRepositoryFilter` will be invoked.
// end::config[]
[[httpsession-xml-sample]]
== httpsession-xml Sample Application
=== Running the httpsession-xml Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
====
----
$ ./gradlew :samples:httpsession-xml:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the httpsession-xml 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-xml/src/main/java/sample/SessionServlet.java[tags=class]
----
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 https://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.

View File

@@ -0,0 +1,171 @@
= 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` with Java Configuration.
NOTE: The completed guide can be found in the <<httpsession-sample, httpsession 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>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>{lettuce-version}</version>
</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::[]
// tag::config[]
[[httpsession-spring-configuration]]
== Spring Java 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[tags=class]
----
<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.
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, refer to the https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
== Java 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[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`.
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:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
====
----
$ ./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[tags=class]
----
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 https://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.

View File

@@ -0,0 +1,169 @@
= Spring Session - Mongo Repositories
Jakub Kubrynski
:toc:
This guide describes how to use Spring Session backed by Mongo.
NOTE: The completed guide can be found in the <<mongo-sample, mongo 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-mongo</artifactId>
<version>{spring-session-version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</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::[]
[[mongo-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.
// tag::config[]
All you have to do is to add the following Spring Configuration:
[source,java]
----
include::{samples-dir}mongo/src/main/java/sample/config/HttpSessionConfig.java[tags=class]
----
<1> The `@EnableMongoHttpSession` 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 Mongo.
<2> We explicitly configure `JdkMongoSessionConverter` since Spring Security's objects cannot be automatically persisted using Jackson (the default if Jackson is on the classpath).
// end::config[]
[[boot-mongo-configuration]]
== Configuring the Mongo Connection
Spring Boot automatically creates a `MongoClient` that connects Spring Session to a Mongo Server on localhost on port 27017 (default port).
In a production environment you need to ensure to update your configuration to point to your Mongo server.
For example, you can include the following in your *application.properties*
.src/main/resources/application.properties
----
spring.data.mongodb.host=mongo-srv
spring.data.mongodb.port=27018
spring.data.mongodb.database=prod
----
For more information, refer to https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-mongodb[Connecting to MongoDB] portion of the Spring Boot documentation.
[[boot-servlet-configuration]]
== 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.
[[mongo-sample]]
== Mongo Sample Application
The Mongo Sample Application demonstrates how to use Spring Session to transparently leverage Mongo to back a web application's `HttpSession` when using Spring Boot.
[[mongo-running]]
=== Running the Mongo Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
----
$ ./gradlew :samples:mongo:bootRun
----
You should now be able to access the application at http://localhost:8080/
[[boot-explore]]
=== 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 Mongo rather than Tomcat's `HttpSession` implementation.
[[mongo-how]]
=== How does it work?
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Mongo.
Spring Session replaces the `HttpSession` with an implementation that is backed by Mongo.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Mongo.
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 inspect the session using mongo client. For example, on a Linux based system you can type:
[NOTE]
====
The sample application uses an embedded MongoDB instance that listens on a randomly allocated port.
The port used by embedded MongoDB together with exact command to connect to it is logged during application startup.
====
$ mongo --port ...
> use test
> db.sessions.find().pretty()
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `60f17293-839b-477c-bb92-07a9c3658843` with the value of your SESSION cookie:
> db.sessions.remove({"_id":"60f17293-839b-477c-bb92-07a9c3658843"})
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

View File

@@ -0,0 +1,219 @@
= Spring Session - REST
Rob Winch
:toc:
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using REST endpoints.
NOTE: The completed guide can be found in the <<rest-sample, rest 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>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>{lettuce-version}</version>
</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::[]
// tag::config[]
[[rest-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}rest/src/main/java/sample/HttpSessionConfig.java[tags=class]
----
<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.
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, refer to the https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
<3> We customize Spring Session's HttpSession integration to use HTTP headers to convey the current session information instead of cookies.
== Servlet Container Initialization
Our <<rest-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. We provide the configuration in our Spring `MvcInitializer` as shown below:
.src/main/java/sample/mvc/MvcInitializer.java
[source,java,indent=0]
----
include::{samples-dir}rest/src/main/java/sample/mvc/MvcInitializer.java[tags=config]
----
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` that makes this extremely easy. Simply extend the class with the default constructor as shown below:
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}rest/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`.
// end::config[]
[[rest-sample]]
== rest Sample Application
=== Running the rest Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
====
----
$ ./gradlew :samples:rest:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the rest Sample Application
Try using the application. Use your favorite REST client to request http://localhost:8080/
$ curl -v http://localhost:8080/
Observe that we are prompted for basic authentication. Provide the following information for the username and password:
* **Username** *user*
* **Password** *password*
$ curl -v http://localhost:8080/ -u user:password
In the output you will notice the following:
----
HTTP/1.1 200 OK
...
x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3
{"username":"user"}
----
Specifically, we notice the following things about our response:
* The HTTP Status is now a 200
* We have a header with the name of *x-auth-token* which contains a new session id
* The current username is displayed
We can now use the *x-auth-token* to make another request without providing the username and password again. For example, the following outputs the username just as before:
$ curl -v http://localhost:8080/ -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
The only difference is that the session id is not provided in the response headers because we are reusing an existing session.
If we invalidate the session, then the x-auth-token is displayed in the response with an empty value. For example, the following will invalidate our session:
$ curl -v http://localhost:8080/logout -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
You will see in the output that the x-auth-token provides an empty String indicating that the previous session was invalidated.
----
HTTP/1.1 204 No Content
...
x-auth-token:
----
=== How does it work?
Spring Security interacts with the standard `HttpSession` in `SecurityContextPersistenceFilter`.
Instead of using Tomcat's `HttpSession`, Spring Security is now persisting the values in Redis.
Spring Session creates a header named x-auth-token in your browser that contains the id of your session.
If you like, you can easily see that the session is created in Redis. First create a session using the following:
$ curl -v http://localhost:8080/ -u user:password
In the output you will notice the following:
----
HTTP/1.1 200 OK
...
x-auth-token: 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
{"username":"user"}
----
Now 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 https://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
We can now use the *x-auth-token* to make another request with the session we deleted and observe we are prompted for a authentication. For example, the following returns an HTTP 401:
$ curl -v http://localhost:8080/ -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"

View File

@@ -0,0 +1,173 @@
= 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>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>{lettuce-version}</version>
</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[tags=class]
----
<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.
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, refer to the https://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[tags=class]
----
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[tags=class]
----
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:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
====
----
$ ./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 https://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.

View File

@@ -0,0 +1,161 @@
= 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:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
====
----
$ ./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 an 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 there is an existing 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 https://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[]

View File

@@ -0,0 +1,138 @@
= 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]
----
We can easily update our configuration to use Spring Session's WebSocket support.
For example:
.src/main/java/samples/config/WebSocketConfig.java
[source,java]
----
include::{samples-dir}websocket/src/main/java/sample/config/WebSocketConfig.java[tags=class]
----
To hook in the Spring Session support we only need to change two things:
<1> Instead of extending `AbstractWebSocketMessageBrokerConfigurer` we extend `AbstractSessionWebSocketMessageBrokerConfigurer`
<2> We rename the `registerStompEndpoints` method to `configureStompEndpoints`
What does `AbstractSessionWebSocketMessageBrokerConfigurer` do behind the scenes?
* `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.
// 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]
----
====
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
====
----
$ ./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.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,467 @@
/*
* Copyright 2014-2016 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
*
* https://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 java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheClosedException;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.ClientCacheFactory;
import com.gemstone.gemfire.cache.query.Index;
import com.gemstone.gemfire.cache.server.CacheServer;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.session.ExpiringSession;
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.events.AbstractSessionEvent;
import static org.assertj.core.api.Assertions.assertThat;
/**
* AbstractGemFireIntegrationTests is an abstract base class encapsulating common
* operations for writing Spring Session GemFire integration tests.
*
* @author John Blum
* @since 1.1.0
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.events.AbstractSessionEvent
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.DataPolicy
* @see com.gemstone.gemfire.cache.ExpirationAttributes
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.client.ClientCache
* @see com.gemstone.gemfire.cache.server.CacheServer
*/
public class AbstractGemFireIntegrationTests {
public static final String GEMFIRE_LOG_LEVEL = System
.getProperty("spring.session.data.gemfire.log-level", "warning");
protected static final boolean DEFAULT_ENABLE_QUERY_DEBUGGING = false;
protected static final boolean GEMFIRE_QUERY_DEBUG = Boolean
.getBoolean("spring.session.data.gemfire.query.debug");
protected static final int DEFAULT_GEMFIRE_SERVER_PORT = CacheServer.DEFAULT_PORT;
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20);
protected static final long DEFAULT_WAIT_INTERVAL = 500L;
protected static final File WORKING_DIRECTORY = new File(
System.getProperty("user.dir"));
protected static final String DEFAULT_PROCESS_CONTROL_FILENAME = "process.ctl";
protected static final String GEMFIRE_LOG_FILE_NAME = System
.getProperty("spring.session.data.gemfire.log-file", "server.log");
@Autowired
protected Cache gemfireCache;
@Autowired
protected GemFireOperationsSessionRepository sessionRepository;
@Before
public void setup() {
System.setProperty("gemfire.Query.VERBOSE",
String.valueOf(isQueryDebuggingEnabled()));
}
/* (non-Javadoc) */
protected static File createDirectory(String pathname) {
File directory = new File(WORKING_DIRECTORY, pathname);
assertThat(directory.isDirectory() || directory.mkdirs())
.as(String.format("Failed to create directory (%1$s)", directory))
.isTrue();
directory.deleteOnExit();
return directory;
}
/* (non-Javadoc) */
protected static List<String> createJavaProcessCommandLine(Class<?> type,
String... args) {
List<String> commandLine = new ArrayList<String>();
String javaHome = System.getProperty("java.home");
String javaExe = new File(new File(javaHome, "bin"), "java").getAbsolutePath();
commandLine.add(javaExe);
commandLine.add("-server");
commandLine.add("-ea");
commandLine.add(String.format("-Dgemfire.log-file=%1$s", GEMFIRE_LOG_FILE_NAME));
commandLine.add(String.format("-Dgemfire.log-level=%1$s", GEMFIRE_LOG_LEVEL));
commandLine
.add(String.format("-Dgemfire.Query.VERBOSE=%1$s", GEMFIRE_QUERY_DEBUG));
commandLine.addAll(extractJvmArguments(args));
commandLine.add("-classpath");
commandLine.add(System.getProperty("java.class.path"));
commandLine.add(type.getName());
commandLine.addAll(extractProgramArguments(args));
// System.err.printf("Java process command-line is (%1$s)%n", commandLine);
return commandLine;
}
/* (non-Javadoc) */
protected static List<String> extractJvmArguments(final String... args) {
List<String> jvmArgs = new ArrayList<String>(args.length);
for (String arg : args) {
if (arg.startsWith("-")) {
jvmArgs.add(arg);
}
}
return jvmArgs;
}
/* (non-Javadoc) */
protected static List<String> extractProgramArguments(final String... args) {
List<String> jvmArgs = new ArrayList<String>(args.length);
for (String arg : args) {
if (!arg.startsWith("-")) {
jvmArgs.add(arg);
}
}
return jvmArgs;
}
/* (non-Javadoc) */
protected static Process run(Class<?> type, File directory, String... args)
throws IOException {
return new ProcessBuilder().command(createJavaProcessCommandLine(type, args))
.directory(directory).start();
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(CacheServer cacheServer) {
return waitForCacheServerToStart(cacheServer, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(CacheServer cacheServer,
long duration) {
return waitForCacheServerToStart(cacheServer.getBindAddress(),
cacheServer.getPort(), duration);
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(String host, int port) {
return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(final String host, final int port,
long duration) {
return waitOnCondition(new Condition() {
AtomicBoolean connected = new AtomicBoolean(false);
public boolean evaluate() {
Socket socket = null;
try {
if (!connected.get()) {
socket = new Socket(host, port);
connected.set(true);
}
}
catch (IOException ignore) {
}
finally {
GemFireUtils.close(socket);
}
return connected.get();
}
}, duration);
}
// NOTE this method would not be necessary except Spring Sessions' build does not fork
// the test JVM
// for every test class.
/* (non-Javadoc) */
protected static boolean waitForClientCacheToClose() {
return waitForClientCacheToClose(DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static boolean waitForClientCacheToClose(long duration) {
try {
final ClientCache clientCache = ClientCacheFactory.getAnyInstance();
clientCache.close();
waitOnCondition(new Condition() {
public boolean evaluate() {
return clientCache.isClosed();
}
}, duration);
return clientCache.isClosed();
}
catch (CacheClosedException ignore) {
return true;
}
}
/* (non-Javadoc) */
protected static boolean waitForProcessToStart(Process process, File directory) {
return waitForProcessToStart(process, directory, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
@SuppressWarnings("all")
protected static boolean waitForProcessToStart(Process process, File directory,
long duration) {
final File processControl = new File(directory, DEFAULT_PROCESS_CONTROL_FILENAME);
waitOnCondition(new Condition() {
public boolean evaluate() {
return processControl.isFile();
}
}, duration);
return process.isAlive();
}
/* (non-Javadoc) */
protected static int waitForProcessToStop(Process process, File directory) {
return waitForProcessToStop(process, directory, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static int waitForProcessToStop(Process process, File directory,
long duration) {
final long timeout = (System.currentTimeMillis() + duration);
try {
while (process.isAlive() && System.currentTimeMillis() < timeout) {
if (process.waitFor(DEFAULT_WAIT_INTERVAL, TimeUnit.MILLISECONDS)) {
return process.exitValue();
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return (process.isAlive() ? -1 : process.exitValue());
}
/* (non-Javadoc) */
protected static boolean waitOnCondition(Condition condition) {
return waitOnCondition(condition, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
@SuppressWarnings("all")
protected static boolean waitOnCondition(Condition condition, long duration) {
final long timeout = (System.currentTimeMillis() + duration);
try {
while (!condition.evaluate() && System.currentTimeMillis() < timeout) {
synchronized (condition) {
TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL);
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return condition.evaluate();
}
/* (non-Javadoc) */
protected static File writeProcessControlFile(File path) throws IOException {
assertThat(path != null && path.isDirectory()).isTrue();
File processControl = new File(path, DEFAULT_PROCESS_CONTROL_FILENAME);
assertThat(processControl.createNewFile()).isTrue();
processControl.deleteOnExit();
return processControl;
}
/* (non-Javadoc) */
protected void assertRegion(Region<?, ?> actualRegion, String expectedName,
DataPolicy expectedDataPolicy) {
assertThat(actualRegion).isNotNull();
assertThat(actualRegion.getName()).isEqualTo(expectedName);
assertThat(actualRegion.getFullPath())
.isEqualTo(GemFireUtils.toRegionPath(expectedName));
assertThat(actualRegion.getAttributes()).isNotNull();
assertThat(actualRegion.getAttributes().getDataPolicy())
.isEqualTo(expectedDataPolicy);
}
/* (non-Javadoc) */
protected void assertIndex(Index index, String expectedExpression,
String expectedFromClause) {
assertThat(index).isNotNull();
assertThat(index.getIndexedExpression()).isEqualTo(expectedExpression);
assertThat(index.getFromClause()).isEqualTo(expectedFromClause);
}
/* (non-Javadoc) */
protected void assertEntryIdleTimeout(Region<?, ?> region,
ExpirationAction expectedAction, int expectedTimeout) {
assertEntryIdleTimeout(region.getAttributes().getEntryIdleTimeout(),
expectedAction, expectedTimeout);
}
/* (non-Javadoc) */
protected void assertEntryIdleTimeout(ExpirationAttributes actualExpirationAttributes,
ExpirationAction expectedAction, int expectedTimeout) {
assertThat(actualExpirationAttributes).isNotNull();
assertThat(actualExpirationAttributes.getAction()).isEqualTo(expectedAction);
assertThat(actualExpirationAttributes.getTimeout()).isEqualTo(expectedTimeout);
}
/* (non-Javadoc) */
protected boolean enableQueryDebugging() {
return DEFAULT_ENABLE_QUERY_DEBUGGING;
}
/* (non-Javadoc) */
protected boolean isQueryDebuggingEnabled() {
return (GEMFIRE_QUERY_DEBUG || enableQueryDebugging());
}
/* (non-Javadoc) */
protected List<String> listRegions(GemFireCache gemfireCache) {
Set<Region<?, ?>> regions = gemfireCache.rootRegions();
List<String> regionList = new ArrayList<String>(regions.size());
for (Region<?, ?> region : regions) {
regionList.add(region.getFullPath());
}
return regionList;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
protected <T extends ExpiringSession> T createSession() {
T expiringSession = (T) this.sessionRepository.createSession();
assertThat(expiringSession).isNotNull();
return expiringSession;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
protected <T extends ExpiringSession> T createSession(String principalName) {
GemFireOperationsSessionRepository.GemFireSession session = createSession();
session.setPrincipalName(principalName);
return (T) session;
}
/* (non-Javadoc) */
protected <T extends ExpiringSession> T expire(T session) {
session.setLastAccessedTime(0L);
return session;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
protected <T extends ExpiringSession> T get(String sessionId) {
return (T) this.sessionRepository.getSession(sessionId);
}
/* (non-Javadoc) */
protected <T extends ExpiringSession> T save(T session) {
this.sessionRepository.save(session);
return session;
}
/* (non-Javadoc) */
protected <T extends ExpiringSession> T touch(T session) {
session.setLastAccessedTime(System.currentTimeMillis());
return session;
}
/**
* The SessionEventListener class is a Spring {@link ApplicationListener} listening
* for Spring HTTP Session application events.
*
* @see org.springframework.context.ApplicationListener
* @see org.springframework.session.events.AbstractSessionEvent
*/
public static class SessionEventListener
implements ApplicationListener<AbstractSessionEvent> {
private volatile AbstractSessionEvent sessionEvent;
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
public <T extends AbstractSessionEvent> T getSessionEvent() {
T sessionEvent = (T) this.sessionEvent;
this.sessionEvent = null;
return sessionEvent;
}
/* (non-Javadoc) */
public void onApplicationEvent(AbstractSessionEvent event) {
this.sessionEvent = event;
}
/* (non-Javadoc) */
public <T extends AbstractSessionEvent> T waitForSessionEvent(long duration) {
waitOnCondition(new Condition() {
public boolean evaluate() {
return (SessionEventListener.this.sessionEvent != null);
}
}, duration);
return getSessionEvent();
}
}
/**
* The Condition interface defines a logical condition that must be satisfied before
* it is safe to proceed.
*/
protected interface Condition {
boolean evaluate();
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2014-2016 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
*
* https://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.http;
import java.util.Map;
import java.util.Properties;
import docs.AbstractGemFireIntegrationTests;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class HttpSessionGemFireIndexingITests extends AbstractGemFireIntegrationTests {
@Test
public void findByIndexName() {
ExpiringSession session = sessionRepository.createSession();
String username = "HttpSessionGemFireIndexingITests-findByIndexName-username";
// tag::findbyindexname-set[]
String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
session.setAttribute(indexName, username);
// end::findbyindexname-set[]
sessionRepository.save(session);
// tag::findbyindexname-get[]
Map<String, ExpiringSession> idToSessions = sessionRepository
.findByIndexNameAndIndexValue(indexName, username);
// end::findbyindexname-get[]
assertThat(idToSessions.keySet()).containsOnly(session.getId());
sessionRepository.delete(session.getId());
}
@Test
@WithMockUser("HttpSessionGemFireIndexingITests-findBySpringSecurityIndexName")
public void findBySpringSecurityIndexName() {
ExpiringSession session = sessionRepository.createSession();
// tag::findbyspringsecurityindexname-context[]
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
// end::findbyspringsecurityindexname-context[]
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
context);
sessionRepository.save(session);
// tag::findbyspringsecurityindexname-get[]
String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
Map<String, ExpiringSession> idToSessions = sessionRepository
.findByIndexNameAndIndexValue(indexName, authentication.getName());
// end::findbyspringsecurityindexname-get[]
assertThat(idToSessions.keySet()).containsOnly(session.getId());
sessionRepository.delete(session.getId());
}
@Configuration
@EnableGemFireHttpSession
static class Config {
@Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", Config.class.getName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
return gemfireProperties;
}
@Bean
CacheFactoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setClose(true);
gemfireCache.setProperties(gemfireProperties());
return gemfireCache;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2014-2016 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
*
* https://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.http.gemfire.indexablesessionattributes;
import java.util.Properties;
import docs.AbstractGemFireIntegrationTests;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
/**
* @author Rob Winch
*
*/
// tag::class-start[]
@EnableGemFireHttpSession(indexableSessionAttributes = { "name1", "name2", "name3" })
public class GemFireHttpSessionConfig {
// end::class-start[]
@Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", GemFireHttpSessionConfig.class.getName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level",
AbstractGemFireIntegrationTests.GEMFIRE_LOG_LEVEL);
return gemfireProperties;
}
@Bean
CacheFactoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setClose(true);
gemfireCache.setProperties(gemfireProperties());
return gemfireCache;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2014-2016 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
*
* https://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.http.gemfire.indexablesessionattributes;
import java.util.Map;
import docs.AbstractGemFireIntegrationTests;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.session.ExpiringSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = GemFireHttpSessionConfig.class)
public class HttpSessionGemFireIndexingCustomITests
extends AbstractGemFireIntegrationTests {
@Test
public void findByIndexName() {
ExpiringSession session = sessionRepository.createSession();
String attrValue = "HttpSessionGemFireIndexingCustomITests-findByIndexName";
// tag::findbyindexname-set[]
String indexName = "name1";
session.setAttribute(indexName, attrValue);
// end::findbyindexname-set[]
sessionRepository.save(session);
// tag::findbyindexname-get[]
Map<String, ExpiringSession> idToSessions = sessionRepository
.findByIndexNameAndIndexValue(indexName, attrValue);
// end::findbyindexname-get[]
assertThat(idToSessions.keySet()).containsOnly(session.getId());
sessionRepository.delete(session.getId());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -18,10 +18,10 @@ package docs;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
@@ -30,17 +30,13 @@ import org.springframework.session.Session;
* @author Rob Winch
*
*/
@RunWith(MockitoJUnitRunner.class)
public class FindByIndexNameSessionRepositoryTests {
@Mock
FindByIndexNameSessionRepository<Session> sessionRepository;
@Mock
Session session;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void setUsername() {
// tag::set-username[]
@@ -56,7 +52,9 @@ public class FindByIndexNameSessionRepositoryTests {
// tag::findby-username[]
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
.findByPrincipalName(username);
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username);
// end::findby-username[]
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,15 +16,15 @@
package docs;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.Session;
import org.springframework.session.ExpiringSession;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,12 +33,12 @@ import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
*/
@ExtendWith(SpringExtension.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class HttpSessionConfigurationNoOpConfigureRedisActionXmlTests {
@Autowired
SessionRepositoryFilter<? extends Session> filter;
SessionRepositoryFilter<? extends ExpiringSession> filter;
@Test
public void redisConnectionFactoryNotUsedSinceNoValidation() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,28 +16,21 @@
package docs;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.junit.jupiter.api.Test;
import com.hazelcast.core.IMap;
import org.junit.Test;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
@@ -56,8 +49,8 @@ public class IndexDocTests {
@Test
public void repositoryDemo() {
RepositoryDemo<MapSession> demo = new RepositoryDemo<>();
demo.repository = new MapSessionRepository(new ConcurrentHashMap<>());
RepositoryDemo<ExpiringSession> demo = new RepositoryDemo<ExpiringSession>();
demo.repository = new MapSessionRepository();
demo.demo();
}
@@ -75,7 +68,7 @@ public class IndexDocTests {
this.repository.save(toSave); // <4>
S session = this.repository.findById(toSave.getId()); // <5>
S session = this.repository.getSession(toSave.getId()); // <5>
// <6>
User user = session.getAttribute(ATTR_USER);
@@ -88,24 +81,24 @@ public class IndexDocTests {
@Test
public void expireRepositoryDemo() {
ExpiringRepositoryDemo<MapSession> demo = new ExpiringRepositoryDemo<>();
demo.repository = new MapSessionRepository(new ConcurrentHashMap<>());
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
demo.repository = new MapSessionRepository();
demo.demo();
}
// tag::expire-repository-demo[]
public class ExpiringRepositoryDemo<S extends Session> {
public class ExpiringRepositoryDemo<S extends ExpiringSession> {
private SessionRepository<S> repository; // <1>
public void demo() {
S toSave = this.repository.createSession(); // <2>
// ...
toSave.setMaxInactiveInterval(Duration.ofSeconds(30)); // <3>
toSave.setMaxInactiveIntervalInSeconds(30); // <3>
this.repository.save(toSave); // <4>
S session = this.repository.findById(toSave.getId()); // <5>
S session = this.repository.getSession(toSave.getId()); // <5>
// ...
}
@@ -117,41 +110,17 @@ public class IndexDocTests {
@SuppressWarnings("unused")
public void newRedisOperationsSessionRepository() {
// tag::new-redisoperationssessionrepository[]
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// ... configure redisTemplate ...
SessionRepository<? extends Session> repository =
new RedisOperationsSessionRepository(redisTemplate);
LettuceConnectionFactory factory = new LettuceConnectionFactory();
SessionRepository<? extends ExpiringSession> repository = new RedisOperationsSessionRepository(
factory);
// end::new-redisoperationssessionrepository[]
}
@Test
@SuppressWarnings("unused")
public void newReactiveRedisOperationsSessionRepository() {
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory();
RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
.<String, Object>newSerializationContext(
new JdkSerializationRedisSerializer())
.build();
// tag::new-reactiveredisoperationssessionrepository[]
// ... create and configure connectionFactory and serializationContext ...
ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>(
connectionFactory, serializationContext);
ReactiveSessionRepository<? extends Session> repository =
new ReactiveRedisOperationsSessionRepository(redisTemplate);
// end::new-reactiveredisoperationssessionrepository[]
}
@Test
@SuppressWarnings("unused")
public void mapRepository() {
// tag::new-mapsessionrepository[]
SessionRepository<? extends Session> repository = new MapSessionRepository(
new ConcurrentHashMap<>());
SessionRepository<? extends ExpiringSession> repository = new MapSessionRepository();
// end::new-mapsessionrepository[]
}
@@ -167,7 +136,7 @@ public class IndexDocTests {
// ... configure transactionManager ...
SessionRepository<? extends Session> repository =
SessionRepository<? extends ExpiringSession> repository =
new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
// end::new-jdbcoperationssessionrepository[]
}
@@ -183,8 +152,11 @@ public class IndexDocTests {
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
IMap<String, MapSession> sessions = hazelcastInstance
.getMap("spring:session:sessions");
HazelcastSessionRepository repository =
new HazelcastSessionRepository(hazelcastInstance);
new HazelcastSessionRepository(sessions);
// end::new-hazelcastsessionrepository[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,8 +16,8 @@
package docs;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -25,7 +25,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.mockito.Mockito.mock;
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
*/
@ExtendWith(SpringExtension.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,8 +16,6 @@
package docs;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSessionRepository;
@@ -29,7 +27,7 @@ import org.springframework.session.config.annotation.web.http.EnableSpringHttpSe
public class SpringHttpSessionConfig {
@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
return new MapSessionRepository();
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,10 +16,8 @@
package docs.http;
import java.util.Properties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
@@ -29,20 +27,18 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.core.session.SessionDestroyedEvent;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
* @author Mark Paluch
* @since 1.2
*/
@ExtendWith(SpringExtension.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public abstract class AbstractHttpSessionListenerTests {
@Autowired
@@ -67,7 +63,6 @@ public abstract class AbstractHttpSessionListenerTests {
RedisConnection connection = mock(RedisConnection.class);
given(factory.getConnection()).willReturn(connection);
given(connection.getConfig(anyString())).willReturn(new Properties());
return factory;
}
@@ -82,7 +77,6 @@ public abstract class AbstractHttpSessionListenerTests {
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.
* springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(SessionDestroyedEvent event) {
this.event = event;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2016 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.
@@ -41,7 +41,7 @@ public class HazelcastHttpSessionConfig {
Config config = new Config();
config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
config.getMapConfig("spring:session:sessions") // <2>
.addMapAttributeConfig(attributeConfig)
.addMapIndexConfig(new MapIndexConfig(
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2016 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
*
* https://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.http;
import java.util.Collections;
import com.fasterxml.jackson.databind.Module;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
/**
*
* @author Jakub Kubrynski
* @author Rob Winch
*/
// tag::config[]
@Configuration
@EnableMongoHttpSession
public class MongoJacksonSessionConfiguration {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter(getJacksonModules());
}
public Iterable<Module> getJacksonModules() {
return Collections.<Module>singletonList(new MyJacksonModule());
}
}
// end::config[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,18 +14,27 @@
* limitations under the License.
*/
package sample.config;
package docs.http;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
/**
*
* @author Jakub Kubrynski
* @author Rob Winch
*/
// tag::config[]
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@EnableMongoHttpSession
public class MongoJdkSessionConfiguration {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JdkMongoSessionConverter();
}
}
// end::config[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
/**
* Spring Session reactive web support.
*/
@NonNullApi
package org.springframework.session.web.server.session;
package docs.http;
import org.springframework.lang.NonNullApi;
import com.fasterxml.jackson.databind.module.SimpleModule;
class MyJacksonModule extends SimpleModule {
}

View File

@@ -16,8 +16,6 @@
package docs.security;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -66,13 +64,15 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
@Override
@Bean
public InMemoryUserDetailsManager userDetailsService() {
return new InMemoryUserDetailsManager(User.withUsername("user")
.password("{noop}password").roles("USER").build());
InMemoryUserDetailsManager uds = new InMemoryUserDetailsManager();
uds.createUser(
User.withUsername("user").password("password").roles("USER").build());
return uds;
}
@Bean
MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
return new MapSessionRepository();
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,21 +16,20 @@
package docs.security;
import java.time.Duration;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.Cookie;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -43,13 +42,12 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
@WebAppConfiguration
@SuppressWarnings("rawtypes")
public class RememberMeSecurityConfigurationTests<T extends Session> {
public class RememberMeSecurityConfigurationTests<T extends ExpiringSession> {
@Autowired
WebApplicationContext context;
@Autowired
@@ -59,7 +57,7 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
MockMvc mockMvc;
@BeforeEach
@Before
public void setup() {
// @formatter:off
this.mockMvc = MockMvcBuilders
@@ -81,12 +79,10 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
Cookie cookie = result.getResponse().getCookie("SESSION");
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
T session = this.sessions
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
assertThat(session.getMaxInactiveInterval())
.isEqualTo(Duration.ofDays(30));
T session = this.sessions.getSession(cookie.getValue());
assertThat(session.getMaxInactiveIntervalInSeconds())
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,21 +16,20 @@
package docs.security;
import java.time.Duration;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.Cookie;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -43,13 +42,12 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
@SuppressWarnings("rawtypes")
public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
public class RememberMeSecurityConfigurationXmlTests<T extends ExpiringSession> {
@Autowired
WebApplicationContext context;
@Autowired
@@ -59,7 +57,7 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
MockMvc mockMvc;
@BeforeEach
@Before
public void setup() {
// @formatter:off
this.mockMvc = MockMvcBuilders
@@ -81,12 +79,10 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
Cookie cookie = result.getResponse().getCookie("SESSION");
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
T session = this.sessions
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
assertThat(session.getMaxInactiveInterval())
.isEqualTo(Duration.ofDays(30));
T session = this.sessions.getSession(cookie.getValue());
assertThat(session.getMaxInactiveIntervalInSeconds())
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
}
}
// end::class[]

View File

@@ -22,7 +22,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
/**
@@ -30,26 +29,24 @@ import org.springframework.session.security.SpringSessionBackedSessionRegistry;
*/
// tag::class[]
@Configuration
public class SecurityConfiguration<S extends Session>
extends WebSecurityConfigurerAdapter {
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private FindByIndexNameSessionRepository<S> sessionRepository;
private FindByIndexNameSessionRepository sessionRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
// other config goes here...
.sessionManagement()
.maximumSessions(2)
.sessionRegistry(sessionRegistry());
// @formatter:on
}
@Bean
public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
@SuppressWarnings("unchecked")
public SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry(this.sessionRepository);
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2016 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.
@@ -19,9 +19,9 @@ 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;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* @author Rob Winch
@@ -30,9 +30,8 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages").withSockJS();
}

View File

@@ -20,13 +20,9 @@
<security:user-service>
<security:user name="user" password="{noop}password" authorities="ROLE_USER"/>
<security:user name="user" password="password" authorities="ROLE_USER"/>
</security:user-service>
<bean class="org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration"/>
<bean id="springSessionRepository" class="org.springframework.session.MapSessionRepository">
<constructor-arg>
<bean class="java.util.concurrent.ConcurrentHashMap"/>
</constructor-arg>
</bean>
<bean id="springSessionRepository" class="org.springframework.session.MapSessionRepository"/>
</beans>

View File

@@ -1,37 +1,166 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"https://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<!-- Suppressions -->
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
<property name="file" value="${configDir}/suppressions.xml"/>
</module>
<module name="io.spring.javaformat.checkstyle.SpringChecks"/>
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
<property name="regexp" value="true"/>
<property name="illegalPkgs"
value="^sun.*, ^org\.apache\.commons\.(?!compress|dbcp2|lang|lang3|logging|pool2).*, ^com\.google\.common.*, ^org\.flywaydb\.core\.internal.*, ^org\.testcontainers\.shaded.*"/>
<property name="illegalClasses"
value="^reactor\.core\.support\.Assert, ^org\.junit\.rules\.ExpectedException, ^org\.slf4j\.LoggerFactory"/>
<!-- Root Checks -->
<module name="RegexpHeader">
<property name="headerFile" value="${configDir}/header.txt"/>
<property name="fileExtensions" value="java"/>
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
<property name="fileExtensions" value="java,xml"/>
</module>
<!-- TreeWalker Checks -->
<module name="TreeWalker">
<!-- Annotations -->
<module name="AnnotationUseStyle">
<property name="elementStyle" value="compact"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<module name="MissingOverride"/>
<module name="PackageAnnotation"/>
<module name="AnnotationLocation">
<property name="allowSamelineSingleParameterlessAnnotation" value="false" />
</module>
<!-- Block Checks -->
<module name="EmptyBlock">
<property name="option" value="text"/>
</module>
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
</module>
<module name="NeedBraces"/>
<module name="AvoidNestedBlocks"/>
<!-- Class Design -->
<module name="FinalClass"/>
<module name="InterfaceIsType"/>
<module name="HideUtilityClassConstructor"/>
<module name="MutableException"/>
<module name="InnerTypeLast"/>
<module name="OneTopLevelClass"/>
<!-- Coding -->
<module name="CovariantEquals"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="InnerAssignment"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="NestedForDepth">
<property name="max" value="3"/>
</module>
<module name="NestedIfDepth">
<property name="max" value="3"/>
</module>
<module name="NestedTryDepth">
<property name="max" value="3"/>
</module>
<module name="MultipleVariableDeclarations"/>
<module name="RequireThis">
<property name="checkMethods" value="false"/>
</module>
<module name="OneStatementPerLine"/>
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="AvoidStaticImport">
<property name="excludes"
value="org.assertj.core.api.Assertions.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.AdditionalMatchers.*, org.mockito.Matchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultHandlers.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo"/>
</module>
<module name="IllegalImport"/>
<module name="RedundantImport"/>
<module name="UnusedImports">
<property name="processJavadoc" value="true"/>
</module>
<module name="ImportOrder">
<property name="groups" value="java,/^javax?\./,*,org.springframework"/>
<property name="ordered" value="true"/>
<property name="separated" value="true"/>
<property name="option" value="bottom"/>
<property name="sortStaticImportsAlphabetically" value="true"/>
</module>
<!-- Javadoc Comments -->
<module name="JavadocType">
<property name="scope" value="package"/>
<property name="authorFormat" value=".+\s.+"/>
</module>
<module name="JavadocMethod">
<property name="allowMissingJavadoc" value="true"/>
</module>
<module name="JavadocVariable">
<property name="scope" value="public"/>
</module>
<module name="JavadocStyle">
<property name="checkEmptyJavadoc" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="JavadocTagContinuationIndentation">
<property name="offset" value="0"/>
</module>
<module name="AtclauseOrder">
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF"/>
<property name="tagOrder" value="@param, @author, @since, @see, @version, @serial, @deprecated"/>
</module>
<module name="AtclauseOrder">
<property name="target" value="METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
<property name="tagOrder" value="@param, @return, @throws, @since, @deprecated, @see"/>
</module>
<!-- Miscellaneous -->
<module name="CommentsIndentation"/>
<module name="UpperEll"/>
<module name="ArrayTypeStyle"/>
<module name="OuterTypeFilename"/>
<!-- Modifiers -->
<module name="RedundantModifier"/>
<!-- Regexp -->
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* +\t*\S"/>
<property name="message" value="Line has leading space characters; indentation should be performed with tabs only."/>
<property name="ignoreComments" value="true"/>
</module>
<module name="RegexpSinglelineJava">
<property name="maximum" value="0"/>
<property name="format" value="org\.mockito\.Mockito\.(when|doThrow|doAnswer)"/>
<property name="message"
value="Please use BDDMockto imports."/>
<property name="ignoreComments" value="true"/>
</module>
<module name="RegexpSinglelineJava">
<property name="maximum" value="0"/>
<property name="format" value="org\.junit\.Assert\.assert"/>
<property name="message" value="Please use AssertJ imports."/>
<property name="ignoreComments" value="true"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="maximum" value="0"/>
<property name="format"
value="assertThatExceptionOfType\((NullPointerException|IllegalArgumentException|IOException|IllegalStateException)\.class\)"/>
<property name="message" value="Please use specialized AssertJ assertThat*Exception method."/>
<property name="ignoreComments" value="true"/>
<module name="Regexp">
<property name="format" value="[ \t]+$"/>
<property name="illegalPattern" value="true"/>
<property name="message" value="Trailing whitespace"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="maximum" value="0"/>
<property name="format" value="org\.mockito\.Mockito\.(when|doThrow|doAnswer)"/>
<property name="message" value="Please use BDDMockito imports."/>
<property name="ignoreComments" value="true"/>
<!-- Whitespace -->
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter">
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS, ARRAY_DECLARATOR"/>
</module>
<module name="NoWhitespaceBefore"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
</module>
</module>

16
etc/checkstyle/header.txt Normal file
View File

@@ -0,0 +1,16 @@
^\Q/*\E$
^\Q * Copyright 2014-\E20\d\d\Q the original author or authors.\E$
^\Q *\E$
^\Q * Licensed under the Apache License, Version 2.0 (the "License");\E$
^\Q * you may not use this file except in compliance with the License.\E$
^\Q * You may obtain a copy of the License at\E$
^\Q *\E$
^\Q * https://www.apache.org/licenses/LICENSE-2.0\E$
^\Q *\E$
^\Q * Unless required by applicable law or agreed to in writing, software\E$
^\Q * distributed under the License is distributed on an "AS IS" BASIS,\E$
^\Q * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\E$
^\Q * See the License for the specific language governing permissions and\E$
^\Q * limitations under the License.\E$
^\Q */\E$
^.*$

View File

@@ -1,11 +1,18 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN"
"https://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress files="[\\/]src[\\/]integration-test[\\/]java[\\/]" checks="Javadoc*"/>
<suppress files="[\\/]spring-session-docs[\\/]" checks="Javadoc*"/>
<suppress files="[\\/]spring-session-docs[\\/]" checks="InnerTypeLast"/>
<suppress files="[\\/]spring-session-samples[\\/]" checks="Javadoc*"/>
<suppress files="[\\/]spring-session-samples[\\/].+Application\.java" checks="HideUtilityClassConstructor"/>
<suppress files="SessionRepositoryFilterTests\.java" checks="SpringLambda"/>
<suppress files=".+Application\.java" checks="HideUtilityClassConstructor"/>
<suppress files=".+Configuration\.java" checks="HideUtilityClassConstructor"/>
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="Javadoc"/>
<suppress files="[\\/]src[\\/]integration-test[\\/]java[\\/]" checks="Javadoc"/>
<suppress files="[\\/]docs[\\/]" checks="Javadoc"/>
<suppress files="[\\/]docs[\\/]" checks="CommentsIndentation"/>
<suppress files="[\\/]docs[\\/]" checks="InnerTypeLast"/>
<suppress files="[\\/]samples[\\/]" checks="Javadoc"/>
<suppress files="[\\/]samples[\\/]" checks="CommentsIndentation"/>
<suppress files="[\\/]samples[\\/]" checks="InnerTypeLast"/>
</suppressions>

File diff suppressed because one or more lines are too long

View File

@@ -1 +1,31 @@
version=2.2.0.M2
bootstrapVersion=2.3.2
commonsPoolVersion=2.4.2
jacksonVersion=2.8.8
jspApiVersion=2.0
servletApiVersion=3.0.1
jstlelVersion=1.2.5
version=1.3.6.BUILD-SNAPSHOT
springDataRedisVersion=1.7.11.RELEASE
html5ShivVersion=3.7.3
commonsLoggingVersion=1.2
junitVersion=4.12
springDataRedisSpring3Version=1.7.1.RELEASE
lettuceVersion=3.5.0.Final
gebVersion=0.13.1
mockitoVersion=1.10.19
hazelcastVersion=3.6.8
seleniumVersion=2.52.0
springDataGeodeVersion=1.0.0.INCUBATING-RELEASE
springSecurityVersion=4.2.11.RELEASE
springVersion=4.3.19.RELEASE
httpClientVersion=4.5.3
h2Version=1.4.195
jedisVersion=2.8.2
springDataMongoVersion=1.9.11.RELEASE
springShellVersion=1.1.0.RELEASE
springDataGemFireVersion=1.8.11.RELEASE
assertjVersion=2.5.0
spockVersion=1.0-groovy-2.4
webjarsTaglibVersion=0.3
jstlVersion=1.2.1
groovyVersion=2.4.11

3
gradle/bom.gradle Normal file
View File

@@ -0,0 +1,3 @@
sonarqube {
skipProject = true
}

View File

@@ -1,33 +0,0 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-M2'
mavenBom 'org.junit:junit-bom:5.4.2'
mavenBom 'org.springframework:spring-framework-bom:5.2.0.M3'
mavenBom 'org.springframework.data:spring-data-releasetrain:Moore-RC1'
mavenBom 'org.springframework.security:spring-security-bom:5.2.0.M3'
mavenBom 'org.testcontainers:testcontainers-bom:1.11.3'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.12.1') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'com.h2database:h2:1.4.199'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8'
dependency 'com.zaxxer:HikariCP:3.3.1'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.1.7.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'mysql:mysql-connector-java:8.0.16'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.12.2'
dependency 'org.hsqldb:hsqldb:2.5.0'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.1'
dependency 'org.mockito:mockito-core:2.28.2'
dependency 'org.postgresql:postgresql:42.2.5'
}
}

53
gradle/ide.gradle Normal file
View File

@@ -0,0 +1,53 @@
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
import org.gradle.plugins.ide.eclipse.model.SourceFolder
apply plugin: "propdeps-eclipse"
apply plugin: "propdeps-idea"
eclipse.project.buildCommand "net.sf.eclipsecs.core.CheckstyleBuilder"
eclipse.project.natures "net.sf.eclipsecs.core.CheckstyleNature"
// Include project specific settings
task eclipseCheckstyle(type: Copy) {
from rootProject.files(
"eclipse/.checkstyle")
into project.projectDir
expand(configDir: rootProject.file('config/checkstyle').absolutePath)
}
task eclipseSettings(type: Copy) {
from rootProject.files(
"eclipse/org.eclipse.jdt.ui.prefs",
"eclipse/org.eclipse.wst.common.project.facet.core.xml")
into project.file('.settings/')
outputs.upToDateWhen { false }
}
task eclipseWstComponent(type: Copy) {
from rootProject.files(
"eclipse/org.eclipse.wst.common.component")
into project.file('.settings/')
expand(deployname: project.name)
outputs.upToDateWhen { false }
}
task eclipseJdtPrepare(type: Copy) {
from rootProject.file("eclipse/org.eclipse.jdt.core.prefs")
into project.file(".settings/")
outputs.upToDateWhen { false }
}
task cleanEclipseJdtUi(type: Delete) {
delete project.file(".settings/org.eclipse.jdt.core.prefs")
delete project.file(".settings/org.eclipse.jdt.ui.prefs")
delete project.file(".settings/org.eclipse.wst.common.component")
delete project.file(".settings/org.eclipse.wst.common.project.facet.core.xml")
}
task eclipseConfiguration(dependsOn: [eclipseCheckstyle, eclipseSettings, eclipseWstComponent]) {
}
tasks["eclipseJdt"].dependsOn(eclipseJdtPrepare)
tasks["cleanEclipse"].dependsOn(cleanEclipseJdtUi)
tasks["eclipse"].dependsOn(eclipseConfiguration)

98
gradle/java.gradle Normal file
View File

@@ -0,0 +1,98 @@
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'javadocHotfix'
apply plugin: 'eclipse-wtp'
apply plugin: 'propdeps'
apply plugin: 'propdeps-idea'
apply plugin: 'propdeps-eclipse'
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'checkstyle'
apply from: IDE_GRADLE
group = 'org.springframework.session'
sourceCompatibility = 1.5
targetCompatibility = 1.5
ext.springIoVersion = project.hasProperty('platformVersion') ? platformVersion : 'Brussels-BUILD-SNAPSHOT'
ext.spockDependencies = [
dependencies.create("org.spockframework:spock-core:$spockVersion") {
exclude group: 'junit', module: 'junit-dep'
}
]
ext.gebDependencies = spockDependencies + [
"org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion",
"org.gebish:geb-spock:$gebVersion",
"org.codehaus.groovy:groovy:$groovyVersion"
]
ext.jstlDependencies = [
"javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:$jstlVersion",
"org.apache.taglibs:taglibs-standard-jstlel:1.2.1"
]
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/libs-snapshot' }
}
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.springframework') {
details.useVersion springVersion
}
}
}
// Integration test setup
configurations {
integrationTestCompile {
extendsFrom testCompile, optional, provided
}
integrationTestRuntime {
extendsFrom integrationTestCompile, testRuntime
}
}
sourceSets {
integrationTest {
java.srcDir file('src/integration-test/java')
groovy.srcDirs file('src/integration-test/groovy')
resources.srcDir file('src/integration-test/resources')
compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integrationTestCompile
runtimeClasspath = output + compileClasspath + configurations.integrationTestRuntime
}
}
task integrationTest(type: Test, dependsOn: jar) {
testClassesDir = sourceSets.integrationTest.output.classesDir
logging.captureStandardOutput(LogLevel.INFO)
classpath = sourceSets.integrationTest.runtimeClasspath
maxParallelForks = 1
reports {
html.destination = project.file("$project.buildDir/reports/integration-tests/")
junitXml.destination = project.file("$project.buildDir/integration-test-results/")
}
}
check.dependsOn integrationTest
checkstyle {
configFile = rootProject.file('etc/checkstyle/checkstyle.xml')
configProperties.configDir = configFile.parentFile
toolVersion = '6.16.1'
}
task checkstyle {
dependsOn project.tasks.findAll { task -> task.name.matches('checkstyle\\w+') }
}
eclipse {
classpath {
plusConfigurations += [ configurations.integrationTestCompile ]
}
}
project.idea.module {
scopes.TEST.plus += [project.configurations.integrationTestRuntime]
}

View File

@@ -0,0 +1,51 @@
apply plugin: 'propdeps-maven'
install {
repositories.mavenInstaller {
customizePom(pom, project)
}
}
def customizePom(pom, gradleProject) {
pom.whenConfigured { generatedPom ->
// sort to make pom dependencies order consistent to ease comparison of older poms
generatedPom.dependencies = generatedPom.dependencies.sort { dep ->
"$dep.scope:$dep.groupId:$dep.artifactId"
}
// add all items necessary for maven central publication
generatedPom.project {
name = gradleProject.description
description = gradleProject.description
url = "https://github.com/spring-projects/spring-session"
organization {
name = "Spring IO"
url = "https://projects.spring.io/spring-session"
}
licenses {
license {
name "The Apache Software License, Version 2.0"
url "https://www.apache.org/licenses/LICENSE-2.0.txt"
distribution "repo"
}
}
scm {
url = "https://github.com/spring-projects/spring-session"
connection = "scm:git:git://github.com/spring-projects/spring-session"
developerConnection = "scm:git:git://github.com/spring-projects/spring-session"
}
developers {
developer {
id = "rwinch"
name = "Rob Winch"
email = "rwinch@pivotal.io"
}
}
issueManagement {
system = "GitHub"
url = "https://github.com/spring-projects/spring-session/issues"
}
}
}
}

4
gradle/sample.gradle Normal file
View File

@@ -0,0 +1,4 @@
tasks.findByPath("artifactoryPublish")?.enabled = false
sonarqube {
skipProject = true
}

27
gradle/spring3.gradle Normal file
View File

@@ -0,0 +1,27 @@
configurations {
spring3TestRuntime.extendsFrom testRuntime
}
configurations.spring3TestRuntime {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.springframework'
&& details.requested.name != 'spring-websocket'
&& details.requested.name != 'spring-messaging') {
details.useVersion '3.2.14.RELEASE'
}
if (details.requested.name == 'spring-data-redis') {
details.useVersion springDataRedisSpring3Version
}
}
}
task spring3Test(type: Test) {
jvmArgs = ['-ea', '-Xmx500m', '-XX:MaxPermSize=128M']
classpath = sourceSets.test.output + sourceSets.main.output + configurations.spring3TestRuntime
exclude "org/springframework/session/web/socket/**"
reports {
html.destination = project.file("$buildDir/spring3-test-results/")
junitXml.destination = project.file("$buildDir/reports/spring3-tests/")
}
}
check.dependsOn spring3Test

63
gradle/tomcat.gradle Normal file
View File

@@ -0,0 +1,63 @@
buildscript {
repositories {
maven { url "https://repo.spring.io/plugins-release" }
}
dependencies {
classpath("com.bmuschko:gradle-tomcat-plugin:2.2.5")
}
}
apply plugin: 'war'
apply plugin: 'com.bmuschko.tomcat'
[tomcatRun,tomcatRunWar]*.contextPath = '/'
task integrationTomcatRun(type: com.bmuschko.gradle.tomcat.tasks.TomcatRun) {
onlyIf { !sourceSets.integrationTest.allSource.empty }
contextPath = tomcatRun.contextPath
daemon = true
tomcatClasspath = tomcatRun.tomcatClasspath
webAppClasspath = tomcatRun.webAppClasspath
webAppSourceDirectory = tomcatRun.webAppSourceDirectory
doFirst {
def mainOutputDir = project.sourceSets.main.output.classesDir
if(mainOutputDir) {
classesDirectory = mainOutputDir
}
// delay reserving ports to ensure they are still available
def ports = reservePorts(3)
httpPort = ports[0]
ajpPort = ports[1]
stopPort = ports[2]
System.setProperty('spring.session.redis.namespace',project.name)
}
}
task integrationTomcatStop(type: com.bmuschko.gradle.tomcat.tasks.TomcatStop) {
onlyIf { !sourceSets.integrationTest.allSource.empty }
doFirst {
stopPort = integrationTomcatRun.stopPort
}
}
integrationTest {
dependsOn integrationTomcatRun
doFirst {
def host = 'localhost:' + integrationTomcatRun.httpPort
systemProperties['geb.build.baseUrl'] = 'http://'+host+'/' + integrationTomcatRun.contextPath
systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
}
finalizedBy integrationTomcatStop
}
def reservePorts(int count) {
def sockets = []
for(int i in 1..count) {
sockets << new ServerSocket(0)
}
def result = sockets*.localPort
sockets*.close()
result
}

8
gradle/tomcat6.gradle Normal file
View File

@@ -0,0 +1,8 @@
apply from: TOMCAT_GRADLE
dependencies {
def tomcatVersion = '6.0.43'
tomcat "org.apache.tomcat:catalina:${tomcatVersion}",
"org.apache.tomcat:coyote:${tomcatVersion}",
"org.apache.tomcat:jasper:${tomcatVersion}"
}

8
gradle/tomcat7.gradle Normal file
View File

@@ -0,0 +1,8 @@
apply from: TOMCAT_GRADLE
dependencies {
def tomcatVersion = '7.0.59'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}

Binary file not shown.

View File

@@ -1,5 +1,6 @@
#Mon Jan 29 18:35:20 CET 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-bin.zip

24
gradlew vendored
View File

@@ -1,21 +1,5 @@
#!/usr/bin/env sh
#
# Copyright 2015 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
@@ -44,16 +28,16 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
warn ( ) {
echo "$*"
}
die () {
die ( ) {
echo
echo "$*"
echo
@@ -171,7 +155,7 @@ if $cygwin ; then
fi
# Escape application args
save () {
save ( ) {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}

18
gradlew.bat vendored
View File

@@ -1,19 +1,3 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -30,7 +14,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

1
samples/boot/README.adoc Normal file
View File

@@ -0,0 +1 @@
Demonstrates using Spring Session with Spring Boot and Spring Security. You can log in with the username "user" and the password "password".

57
samples/boot/build.gradle Normal file
View File

@@ -0,0 +1,57 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
}
}
apply plugin: 'org.springframework.boot'
apply from: JAVA_GRADLE
apply from: SAMPLE_GRADLE
group = 'samples'
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session'),
"org.springframework.boot:spring-boot-starter-data-redis",
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-thymeleaf",
"org.springframework.boot:spring-boot-starter-security",
"nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect",
"org.thymeleaf.extras:thymeleaf-extras-conditionalcomments",
"org.webjars:bootstrap:$bootstrapVersion",
"org.webjars:html5shiv:$html5ShivVersion",
"org.webjars:webjars-locator"
testCompile "org.springframework.boot:spring-boot-starter-test"
integrationTestCompile gebDependencies,
"org.spockframework:spock-spring:$spockVersion"
}
integrationTest {
doFirst {
def port = reservePort()
def host = 'localhost:' + port
systemProperties['geb.build.baseUrl'] = 'http://'+host+'/'
systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
systemProperties['server.port'] = port
systemProperties['management.port'] = 0
systemProperties['spring.session.redis.namespace'] = project.name
}
}
def reservePort() {
def socket = new ServerSocket(0)
def result = socket.localPort
socket.close()
result
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2014-2016 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
*
* https://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 sample
import geb.spock.*
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.IntegrationTest
import org.springframework.boot.test.SpringApplicationConfiguration
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.web.WebAppConfiguration
import sample.pages.HomePage
import sample.pages.LoginPage
import spock.lang.Stepwise
import pages.*
/**
* Tests the demo that supports multiple sessions
*
* @author Rob Winch
*/
@Stepwise
@ContextConfiguration(classes = Application, loader = SpringApplicationContextLoader)
@WebAppConfiguration
@IntegrationTest
class BootTests extends GebReportingSpec {
def 'Unauthenticated user sent to log in page'() {
when: 'unauthenticated user request protected page'
via HomePage
then: 'sent to the log in page'
at LoginPage
}
def 'Log in views home page'() {
when: 'log in successfully'
login()
then: 'sent to original page'
at HomePage
and: 'the username is displayed'
username == 'user'
and: 'Spring Session Management is being used'
driver.manage().cookies.find { it.name == 'SESSION' }
and: 'Standard Session is NOT being used'
!driver.manage().cookies.find { it.name == 'JSESSIONID' }
}
def 'Log out success'() {
when:
logout()
then:
at LoginPage
}
def 'Logged out user sent to log in page'() {
when: 'logged out user request protected page'
via HomePage
then: 'sent to the log in page'
at LoginPage
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2014-2016 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
*
* https://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 sample.pages
import geb.*
/**
* The home page
*
* @author Rob Winch
*/
class HomePage extends Page {
static url = ''
static at = { assert driver.title == 'Spring Session Sample - Secured Content'; true}
static content = {
username { $('#un').text() }
logout(to:LoginPage) { $('input[type=submit]').click() }
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,28 +14,25 @@
* limitations under the License.
*/
package sample.pages;
package sample.pages
import org.openqa.selenium.WebDriver;
import geb.*
/**
* @author Eddú Meléndez
* The Links Page
*
* @author Rob Winch
*/
public class BasePage {
private WebDriver driver;
public BasePage(WebDriver driver) {
this.driver = driver;
class LoginPage extends Page {
static url = '/login'
static at = { assert driver.title == 'Login Page'; true}
static content = {
form { $('form') }
submit { $('input[type=submit]') }
login(required:false) { user='user', pass='password' ->
form.username = user
form.password = pass
submit.click(HomePage)
}
}
public WebDriver getDriver() {
return this.driver;
}
public static void get(WebDriver driver, String get) {
String baseUrl = "http://localhost";
driver.get(baseUrl + get);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2014-2016 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
*
* https://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 sample.config;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
// tag::class[]
@EnableRedisHttpSession // <1>
public class HttpSessionConfig {
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,25 +16,19 @@
package sample.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
/**
* @author Rob Winch
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// @formatter:off
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll();
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
// @formatter:on
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2014-2016 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
*
* https://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 sample.mvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Controller for sending the user to the login view.
*
* @author Rob Winch
*
*/
@Controller
public class IndexController {
@RequestMapping("/")
public String index() {
return "index";
}
}

View File

@@ -0,0 +1,2 @@
spring.thymeleaf.cache=false
spring.template.cache=false

View File

@@ -1,4 +1,4 @@
<html xmlns:th="https://www.thymeleaf.org" xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect" layout:decorate="~{layout}">
<html xmlns:th="https://www.thymeleaf.org" xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect" layout:decorator="layout">
<head>
<title>Secured Content</title>
</head>

View File

@@ -3,8 +3,8 @@
xmlns:th="https://www.thymeleaf.org"
xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect">
<head>
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
<link rel="icon" type="image/x-icon" th:href="@{/favicon.ico}" href="../static/favicon.ico"/>
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
<link rel="icon" type="image/x-icon" th:href="@{/resources/img/favicon.ico}" href="../static/img/favicon.ico"/>
<link th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"></link>
<style type="text/css">
/* Sticky footer styles
@@ -79,7 +79,7 @@
<div class="navbar navbar-inverse navbar-static-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" th:href="@{/}"><img th:src="@{/images/logo.png}" alt="Spring Security Sample"/></a>
<a class="brand" th:href="@{/}"><img th:src="@{/resources/img/logo.png}" alt="Spring Security Sample"/></a>
<div class="nav-collapse collapse"
th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}">
@@ -115,7 +115,7 @@
<div id="footer">
<div class="container">
<p class="muted credit">Visit the <a href="https://projects.spring.io/spring-session/">Spring Session</a> site for more <a href="https://github.com/spring-projects/spring-session/tree/master/samples">samples</a>.</p>
<p class="muted credit">Visit the <a href="https://spring.io/spring-security">Spring Security</a> site for more <a href="https://github.com/spring-projects/spring-security/blob/master/samples/">samples</a>.</p>
</div>
</div>
</body>

View File

@@ -0,0 +1,20 @@
apply from: JAVA_GRADLE
apply from: TOMCAT_7_GRADLE
apply from: SAMPLE_GRADLE
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile "org.springframework:spring-web:$springVersion",
"biz.paluch.redis:lettuce:$lettuceVersion",
"org.webjars:bootstrap:$bootstrapVersion",
"org.webjars:webjars-taglib:$webjarsTaglibVersion",
jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile "junit:junit:$junitVersion"
integrationTestCompile gebDependencies
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,30 +14,33 @@
* limitations under the License.
*/
package sample;
package sample
import geb.spock.*
import sample.pages.HomePage;
import spock.lang.Stepwise
import pages.*
/**
* Tests the CAS sample application using service tickets.
*
* @author Rob Winch
* @since 5.0
*/
public class SessionAttributeForm {
private String attributeName;
private String attributeValue;
public String getAttributeName() {
return this.attributeName;
@Stepwise
class AttributeTests extends GebReportingSpec {
def 'first visit no attributes'() {
when:
to HomePage
then:
attributes.empty
}
public void setAttributeName(String attributeName) {
this.attributeName = attributeName;
}
public String getAttributeValue() {
return this.attributeValue;
}
public void setAttributeValue(String attributeValue) {
this.attributeValue = attributeValue;
def 'create attribute'() {
when:
createAttribute('a','b')
then:
attributes.size() == 1
attributes[0].name == 'a'
attributes[0].value == 'b'
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2014-2016 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
*
* https://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 sample.pages
import geb.*
/**
* The home page
*
* @author Rob Winch
*/
class HomePage extends Page {
static url = ''
static at = { assert driver.title == 'Session Attributes'; true}
static content = {
form { $('form') }
submit { $('input[type=submit]') }
createAttribute(required:false) { name, value ->
form.attributeName = name
form.attributeValue = value
submit.click(HomePage)
}
attributes { moduleList AttributeRow, $("table tr").tail() }
}
}
class AttributeRow extends Module {
static content = {
cell { $("td", it) }
name { cell(0).text() }
value { cell(1).text() }
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2016 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.
@@ -17,13 +17,11 @@
package sample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Import(EmbeddedRedisConfig.class)
@EnableRedisHttpSession
public class Config {

View File

@@ -0,0 +1,4 @@
Demonstrates using Spring Session to lookup a user's session by the username.
The sample provides a hook to add the current username to the session (required for finding the user) by providing a custom implementation of Spring Security's `AuthenticationSuccessHandler`.
NOTE: This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com

Some files were not shown because too many files have changed in this diff Show More