Compare commits

..

6 Commits

Author SHA1 Message Date
Spring Buildmaster
f25bdbab0e Next development version 2016-03-16 08:15:10 -07:00
Spring Buildmaster
b53e52f529 Release version 1.1.1.RELEASE 2016-03-16 08:15:00 -07:00
Rob Winch
debaea0b58 Update New in 1.1 Doc
Fixes gh-390
2016-03-10 10:08:59 -06:00
Rob Winch
f8707009bd Delete refactor from findbyusername doc
Fixes gh-389
2016-03-10 10:05:07 -06:00
Rob Winch
47d897c846 DefaultCookieSerializer ignores null Cookie value
Previously a null Cookie value was returned by DefaultCookieSerializer
readCookieValues(). This could cause NullPointerExeptions later on.

This commit ignores cookies with a null value.

Fixes gh-392
2016-03-04 10:23:16 -06:00
Rob Winch
09d7704c9e Fix GemFire compile in Eclipse
Fixes gh-399
2016-02-29 14:38:54 -06:00
676 changed files with 64346 additions and 35558 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 +0,0 @@
<!--
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 +0,0 @@
<!--
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 #).
-->

3
.gitignore vendored
View File

@@ -10,6 +10,3 @@ target
out
.springBeans
*.rdb
.checkstyle
!etc/eclipse/.checkstyle
!**/src/**/build

View File

@@ -1,20 +0,0 @@
language: java
sudo: required
services: docker
jdk: oraclejdk8
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

View File

@@ -40,5 +40,5 @@ appropriate to the circumstances. Maintainers are obligated to maintain confiden
with regard to the reporter of an incident.
This Code of Conduct is adapted from the
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
http://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
http://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]

View File

@@ -10,8 +10,8 @@ By participating, you are expected to uphold this code. Please report unaccepta
== Using GitHub issues
We use GitHub issues to track bugs and enhancements. If you have a general usage question
please ask on https://stackoverflow.com[Stack Overflow]. The Spring Session team and the
broader community monitor the https://stackoverflow.com/tags/spring-session[`spring-session`]
please ask on http://stackoverflow.com[Stack Overflow]. The Spring Session team and the
broader community monitor the http://stackoverflow.com/tags/spring-session[`spring-session`]
tag.
If you are reporting a bug, please help to speed up problem diagnosis by providing as much
@@ -21,6 +21,12 @@ reproduces the problem.
== Sign the Contributor License Agreement
If you have not previously done so, please fill out and
submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
Before we accept a non-trivial patch or pull request we will need you to sign the
https://support.springsource.com/spring_committer_signup[contributor's agreement].
If you have previously signed the CLA for any project, there is no need to sign up again.
Signing the contributor's agreement does not grant anyone commit rights to the main
repository, but it does mean that we can accept your contributions, and you will get an
author credit if we do. Active contributors might be asked to join the core team, and
given the ability to merge pull requests. Use the project `Spring Security` (the parent project)
and '`Rob Winch`' in the project lead field when you complete the form.

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,24 @@
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
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/
You can find the documentation, issue management, support, samples, and guides for using Spring Session at http://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].
Spring Session is Open Source software released under the http://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].

View File

@@ -1,40 +1,67 @@
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("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3")
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()
}
group = 'org.springframework.session'
description = 'Spring Session'
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = JavaVersion.VERSION_1_8
ext.springBootVersion = '1.3.2.RELEASE'
ext.JAVA_GRADLE = "$rootDir/gradle/java.gradle"
ext.SPRING3_GRADLE = "$rootDir/gradle/spring3.gradle"
ext.MAVEN_GRADLE = "$rootDir/gradle/publish-maven.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: 'sonar-runner'
apply plugin: 'base'
sonarRunner {
sonarProperties {
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']) << {
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
}

72
docs/build.gradle Normal file
View File

@@ -0,0 +1,72 @@
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'),
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
"org.springframework.data:spring-data-redis:$springDataRedisVersion",
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
"org.springframework:spring-websocket:${springVersion}",
"org.springframework:spring-messaging:${springVersion}",
"org.springframework.security:spring-security-web:${springSecurityVersion}",
"org.springframework.security:spring-security-test:${springSecurityVersion}",
'junit:junit:4.11',
'org.mockito:mockito-core:1.9.5',
"org.springframework:spring-test:$springVersion",
"org.assertj:assertj-core:$assertjVersion",
"com.hazelcast:hazelcast:$hazelcastVersion",
"redis.clients:jedis:2.4.1",
"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,
'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/',
'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-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 http://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 http://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 http://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

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 http://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 http://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: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,138 @@
= 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.
[[findbyprincipalnamesessionrepository]]
== FindByIndexNameSessionRepository
In order to look up a user by their username, you must first choose a `SessionRepository` that implements <<index.doc#api-findbyprincipalnamesessionrepository,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 http://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,191 @@
= 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::[]
[[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> 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 http://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 `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}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`.
[[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
http://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 http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-java-client[a Java client],
http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#other-client-implementations[one of the other clients], or the
http://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 http://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
http://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,272 @@
= 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> First, a `Properties` bean is created to reference GemFire configuration common to both the client and server,
stored in the `META-INF/spring/application.properties` file.
<2> The `application.properties` are used along with the `PropertySourcesPlaceholderConfigurer` bean to replace
placeholders in the Spring XML configuration meta-data with property values.
<3> 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.
<4> `GemFireHttpSessionConfiguration` is registered to enable Spring Session functionality.
<5> Then, a Spring `BeanPostProcessor` is registered to determine whether a GemFire Server at the designated host/port
is running, blocking client startup until the server is available.
<6> Next, we include a `Properties` bean to configure certain aspects of the GemFire client cache using
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
In this case, we are just setting GemFire's `log-level` from a sample application-specific System property, defaulting
to `warning` if unspecified.
<7> Finally, we create the GemFire client cache and 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.
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 http://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 http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
=== Server Configuration
Now, we have only covered one side of the equation. We also need a GemFire Server for our client to talk to and pass
session state information up 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 from `META-INF/spring/application.properties`.
<3> We enable the same Spring Session functionality that 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.
<4> 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.
<5> Then, we create an instance of the GemFire peer cache using our GemFire System properties.
<6> And finally, we also setup a GemFire `CacheServer` instance running on *localhost*, listening on port **11235**,
to accept our client connection.
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 http://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 http://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 http://gemfire.docs.pivotal.io/docs-gemfire/latest/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 http://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,274 @@
= 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.
In this instance, Spring Session is backed by GemFire.
<2> Next, we register a `Properties` bean that allows us to configure certain aspects of the GemFire client cache
using http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
<3> 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
http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API].
<4> After configuring a `Pool`, we create an instance of the GemFire client cache using the GemFire `Properties`
and `Pool` to communicate with the server and perform cache data access operations.
<5> 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. This `BeanPostProcessor` implements 2 approaches to ensure our server has adequate
time to startup.
The first approach uses a timed wait, checking at periodic intervals to determine whether a client `Socket` connection
can be made to the server's `CacheServer` endpoint.
The second approach uses a GemFire http://gemfire.docs.pivotal.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. Either one of these approaches are sufficient
by themselves, but both are demonstrated here to illustrate how this might work and to give you ideas, or other options
in practice.
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 http://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 http://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 http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies]
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`).
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
Now, we have only covered one side of the equation. We also need a GemFire Server for our client to talk to and pass
session state up 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. For one, this
ensures that 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 the GemFire peer cache using our GemFire System properties.
<4> We also setup a GemFire `CacheServer` instance running on **localhost**, listening on port **12480**,
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 http://gemfire.docs.pivotal.io/docs-gemfire/latest/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 http://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 http://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 http://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 http://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 http://gemfire.docs.pivotal.io/docs-gemfire/latest/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 http://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 http://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 http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies]
with a GemFire http://gemfire.docs.pivotal.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 http://gemfire.docs.pivotal.io/docs-gemfire/latest/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 http://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,174 @@
= 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>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 http://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 http://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 http://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 http://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: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 http://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.

View File

@@ -0,0 +1,166 @@
= 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>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 http://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 http://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: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 http://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.

View File

@@ -0,0 +1,214 @@
= 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>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 http://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 http://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: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 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 http://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
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,168 @@
= Spring Session and Spring Security
Rob Winch
:toc:
This guide describes how to use Spring Session along with Spring Security.
It assumes you have already applied Spring Security to your application.
NOTE: The completed guide can be found in the <<security-sample, security sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>{spring-session-version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>{spring-version}</version>
</dependency>
</dependencies>
----
ifeval::["{version-snapshot}" == "true"]
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
----
endif::[]
ifeval::["{version-milestone}" == "true"]
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
.pom.xml
[source,xml]
----
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
endif::[]
[[security-spring-configuration]]
== Spring Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
Add the following Spring Configuration:
[source,java]
----
include::{samples-dir}security/src/main/java/sample/Config.java[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 http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
== Servlet Container Initialization
Our <<security-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
Since our application is already loading Spring configuration using our `SecurityInitializer` class, we can simply add our Config class to it.
.src/main/java/sample/SecurityInitializer.java
[source,java]
----
include::{samples-dir}security/src/main/java/sample/SecurityInitializer.java[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 http://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:security:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the security Sample Application
Try using the application. Enter the following to log in:
* **Username** _user_
* **Password** _password_
Now click the **Login** button.
You should now see a message indicating your are logged in with the user entered previously.
The user's information is stored in Redis rather than Tomcat's `HttpSession` implementation.
=== How does it work?
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
Spring Session replaces the `HttpSession` with an implementation that is backed by Redis.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Redis.
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
$ redis-cli keys '*' | xargs redis-cli del
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

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 http://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: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 http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html#encodeURL(java.lang.String)[HttpServletResponse#encodeURL(java.lang.String)]
This means that if you are using standard tag libraries the session alias is automatically included in the URL.
For example, if we are currently using the session with the alias of *1*, then the following:
.src/main/webapp/index.jsp
[source,xml,indent=0]
----
include::{samples-dir}users/src/main/webapp/index.jsp[tags=link]
----
will output a link of:
[source,html]
----
<a id="navLink" href="/link.jsp?_s=1">Link</a>
----
// end::how-does-it-work[]

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 http://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: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.

View File

@@ -0,0 +1,946 @@
= Spring Session
Rob Winch
:doctype: book
:indexdoc-tests: {docs-test-dir}docs/IndexDocTests.java
:websocketdoc-test-dir: {docs-test-dir}docs/websocket/
:toc: left
[[abstract]]
Spring Session provides an API and implementations for managing a user's session information.
[[introduction]]
== Introduction
Spring Session provides an API and implementations for managing a user's session information. It also provides transparent integration with:
* <<httpsession,HttpSession>> - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way.
Additional features include:
** **Clustered Sessions** - Spring Session makes it trivial to support <<httpsession-redis,clustered sessions>> without being tied to an application container specific solution.
** **Multiple Browser Sessions** - Spring Session supports <<httpsession-multi,managing multiple users' sessions>> in a single browser instance (i.e. multiple authenticated accounts similar to Google).
** **RESTful APIs** - Spring Session allows providing session ids in headers to work with <<httpsession-rest,RESTful APIs>>
* <<websocket,WebSocket>> - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
== What's New in 1.1
Below are the highlights of what is new in Spring Session 1.1. You can find a complete list of what's new in https://github.com/spring-projects/spring-session/issues?utf8=%E2%9C%93&q=milestone%3A%221.1.0+M1%22[1.1.0 M1], https://github.com/spring-projects/spring-session/issues?utf8=%E2%9C%93&q=milestone%3A%221.1.0+RC1%22[1.1.0 RC1], and
https://github.com/spring-projects/spring-session/issues?utf8=%E2%9C%93&q=milestone%3A%221.1.0%22[1.1.0]
by referring to the changelog.
* https://github.com/spring-projects/spring-session/issues/148[#148] - Added <<httpsession-gemfire,GemFire Support>>
* https://github.com/spring-projects/spring-session/issues/7[#7] - link:guides/findbyusername.html[Query by Username]
* https://github.com/spring-projects/spring-session/issues/299[#299] - link:guides/custom-cookie.html[Customize Cookie Creation]
* https://github.com/spring-projects/spring-session/issues/4[#4] - Add <<httpsession-httpsessionlistener,HttpSessionListener>> support
* https://github.com/spring-projects/spring-session/issues/283[#283] - Allow override default `RedisSerializer`
* https://github.com/spring-projects/spring-session/issues/277[#277] - Added link:guides/hazelcast-spring.html[@EnableHazelcastHttpSession]
* https://github.com/spring-projects/spring-session/issues/271[#271] - Performance improvements
* https://github.com/spring-projects/spring-session/pull/218[#218] - Allow scoping the session in Redis using <<api-redisoperationssessionrepository-config,redisNamespace>>
* https://github.com/spring-projects/spring-session/issues/273[#273] - Allow writing to Redis immediately (instead of lazily) using <<api-redisoperationssessionrepository-config,redisFlushMode>>
* https://github.com/spring-projects/spring-session/issues/272[#272] - Add `ExpiringSession.setLastAccessedTime(long)`
* https://github.com/spring-projects/spring-session/pull/349[#349] - Added https://gitter.im/spring-projects/spring-session[Gitter Room] for discussing Spring Session
* https://github.com/spring-projects/spring-session/issues/388[#388] - Support Spring Framework WebSockets
[[samples]]
== Samples and Guides (Start Here)
If you are looking to get started with Spring Session, the best place to start is our Sample Applications.
.Sample Applications
|===
| Source | Description | Guide
| {gh-samples-url}httpsession[HttpSession]
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store.
| link:guides/httpsession.html[HttpSession Guide]
| {gh-samples-url}httpsession-xml[HttpSession XML]
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store using XML based configuration.
| link:guides/httpsession-xml.html[HttpSession XML Guide]
| {gh-samples-url}httpsession-gemfire-clientserver[HttpSession with GemFire (Client/Server)]
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology.
| link:guides/httpsession-gemfire-clientserver.html[HttpSession GemFire Client/Server Guide]
| {gh-samples-url}httpsession-gemfire-clientserver-xml[HttpSession with GemFire (Client/Server) using XML]
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology configured with XML.
| link:guides/httpsession-gemfire-clientserver-xml.html[HttpSession GemFire Client/Server XML Guide]
| {gh-samples-url}httpsession-gemfire-p2p[HttpSession with GemFire (P2P)]
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology.
| link:guides/httpsession-gemfire-p2p.html[HttpSession GemFire P2P Guide]
| {gh-samples-url}httpsession-gemfire-p2p-xml[HttpSession with GemFire (P2P) using XML]
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology configured with XML.
| link:guides/httpsession-gemfire-p2p-xml.html[HttpSession GemFire P2P XML Guide]
| {gh-samples-url}custom-cookie[Custom Cookie]
| Demonstrates how to use Spring Session and customize the cookie.
| link:guides/custom-cookie.html[Custom Cookie Guide]
| {gh-samples-url}boot[Spring Boot]
| Demonstrates how to use Spring Session with Spring Boot.
| link:guides/boot.html[Spring Boot Guide]
| {gh-samples-url}security[Spring Security]
| Demonstrates how to use Spring Session with an existing Spring Security application.
| link:guides/security.html[Spring Security Guide]
| {gh-samples-url}rest[REST]
| Demonstrates how to use Spring Session in a REST application to support authenticating with a header.
| link:guides/rest.html[REST Guide]
| {gh-samples-url}findbyusername[Find by Username]
| Demonstrates how to use Spring Session to find sessions by username.
| link:guides/findbyusername.html[Find by Username]
| {gh-samples-url}users[Multiple Users]
| Demonstrates how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
| link:guides/users.html[Manage Multiple Users Guide]
| {gh-samples-url}websocket[WebSocket]
| Demonstrates how to use Spring Session with WebSockets.
| link:guides/websocket.html[WebSocket Guide]
[[samples-hazelcast]]
| {gh-samples-url}hazelcast[Hazelcast]
| Demonstrates how to use Spring Session with Hazelcast.
| TBD
[[samples-hazelcast-spring]]
| {gh-samples-url}hazelcast-spring[Hazelcast Spring]
| Demonstrates how to use Spring Session and Hazelcast with an existing Spring Security application.
| link:guides/hazelcast-spring.html[Hazelcast Spring Guide]
|===
[[httpsession]]
== HttpSession Integration
Spring Session provides transparent integration with `HttpSession`.
This means that developers can switch the `HttpSession` implementation out with an implementation that is backed by Spring Session.
[[httpsession-why]]
=== Why Spring Session & HttpSession?
We have already mentioned that Spring Session provides transparent integration with `HttpSession`, but what benefits do we get out of this?
* **Clustered Sessions** - Spring Session makes it trivial to support <<httpsession-redis,clustered sessions>> without being tied to an application container specific solution.
* **Multiple Browser Sessions** - Spring Session supports <<httpsession-multi,managing multiple users' sessions>> in a single browser instance (i.e. multiple authenticated accounts similar to Google).
* **RESTful APIs** - Spring Session allows providing session ids in headers to work with <<httpsession-rest,RESTful APIs>>
[[httpsession-redis]]
=== HttpSession with Redis
Using Spring Session with `HttpSession` is enabled by adding a Servlet Filter before anything that uses the `HttpSession`.
You can choose from enabling this using either:
* <<httpsession-redis-jc,Java Based Configuration>>
* <<httpsession-redis-xml,XML Based Configuration>>
[[httpsession-redis-jc]]
==== Redis Java Based Configuration
This section describes how to use Redis to back `HttpSession` using Java based configuration.
NOTE: The <<samples, HttpSession Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using XML configuration.
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession Guide when integrating with your own application.
include::guides/httpsession.adoc[tags=config,leveloffset=+3]
[[httpsession-redis-xml]]
==== Redis XML Based Configuration
This section describes how to use Redis to back `HttpSession` using XML based configuration.
NOTE: The <<samples, HttpSession XML Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using XML configuration.
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession XML Guide when integrating with your own application.
include::guides/httpsession-xml.adoc[tags=config,leveloffset=+3]
[[httpsession-gemfire]]
=== HttpSession with Pivotal GemFire
When https://pivotal.io/big-data/pivotal-gemfire[Pivotal GemFire] is used with Spring Session, a web application's
`HttpSession` can be replaced with a **clustered** implementation managed by GemFire and conveniently accessed
with Spring Session's API.
The two most common topologies to manage Spring Sessions using GemFire include:
* <<httpsession-gemfire-clientserver,Client-Server>>
* <<httpsession-gemfire-p2p,Peer-To-Peer (P2P)>>
Additionally, GemFire supports site-to-site replication using http://gemfire.docs.pivotal.io/docs-gemfire/topologies_and_comm/multi_site_configuration/chapter_overview.html[WAN functionality].
The ability to configure and use GemFire's WAN support is independent of Spring Session, and is beyond the scope
of this document. More details on GemFire WAN functionality can be found http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/#bootstrap:gateway[here].
[[httpsession-gemfire-clientserver]]
==== GemFire Client-Server
The http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client-Server]
topology will probably be the more common configuration preference for users when using GemFire as a provider in
Spring Session since a GemFire server will have significantly different and unique JVM heap requirements when compared
to the application. Using a client-server topology enables an application to manage (e.g. replicate) application state
independently from other application processes.
In a client-server topology, an application using Spring Session will open a client cache connection to a (remote)
GemFire server cluster to manage and provide consistent access to all `HttpSession` state.
You can configure a Client-Server topology with either:
* <<httpsession-gemfire-clientserver-java,Java-based Configuration>>
* <<httpsession-gemfire-clientserver-xml,XML-based Configuration>>
[[httpsession-gemfire-clientserver-java]]
===== GemFire Client-Server Java-based Configuration
This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with Java-based configuration.
NOTE: The <<samples,HttpSession with GemFire (Client-Server) Sample>> provides a working sample on how to integrate
Spring Session and GemFire to replace the HttpSession using Java configuration. You can read the basic steps for
integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server)
Guide when integrating with your own application.
include::guides/httpsession-gemfire-clientserver.adoc[tags=config,leveloffset=+3]
[[http-session-gemfire-clientserver-xml]]
===== GemFire Client-Server XML-based Configuration
This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with XML-based configuration.
NOTE: The <<samples,HttpSession with GemFire (Client-Server) using XML Sample>> provides a working sample on how to
integrate Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps
for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server)
using XML Guide when integrating with your own application.
include::guides/httpsession-gemfire-clientserver-xml.adoc[tags=config,leveloffset=+3]
[[httpsession-gemfire-p2p]]
==== GemFire Peer-To-Peer (P2P)
Perhaps less common would be to configure the Spring Session application as a peer member in the GemFire cluster using
the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/p2p_configuration/chapter_overview.html[Peer-To-Peer (P2P)] topology.
In this configuration, a Spring Session application would be an actual data node (server) in the GemFire cluster,
and **not** a cache client as before.
One advantage to this approach is the proximity of the application to the application's state (i.e. it's data). However,
there are other effective means of accomplishing similar data dependent computations, such as using GemFire's
http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/function_exec/chapter_overview.html[Function Execution].
Any of GemFire's other http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/product_intro.html[features]
can be used when GemFire is serving as a provider in Spring Session.
P2P is very useful for both testing purposes as well as smaller, more focused and self-contained applications,
such as those found in a microservices architecture, and will most certainly improve on your application's latency,
throughput and consistency needs.
You can configure a Peer-To-Peer (P2P) topology with either:
* <<httpsession-gemfire-p2p-java,Java-based Configuration>>
* <<httpsession-gemfire-p2p-xml,XML-based Configuration>>
[[httpsession-gemfire-p2p-java]]
===== GemFire Peer-To-Peer (P2P) Java-based Configuration
This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using Java-based configuration.
NOTE: The <<samples, HttpSession with GemFire (P2P) Sample>> provides a working sample on how to integrate
Spring Session and GemFire to replace the `HttpSession` using Java configuration. You can read the basic steps
for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) Guide
when integrating with your own application.
include::guides/httpsession-gemfire-p2p.adoc[tags=config,leveloffset=+3]
[[httpsession-gemfire-p2p-xml]]
===== GemFire Peer-To-Peer (P2P) XML-based Configuration
This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using XML-based configuration.
NOTE: The <<samples, HttpSession with GemFire (P2P) using XML Sample>> provides a working sample on how to integrate
Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps for
integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) using XML
Guide when integrating with your own application.
include::guides/httpsession-gemfire-p2p-xml.adoc[tags=config,leveloffset=+3]
[[httpsession-how]]
=== How HttpSession Integration Works
Fortunately both `HttpSession` and `HttpServletRequest` (the API for obtaining an `HttpSession`) are both interfaces.
This means that we can provide our own implementations for each of these APIs.
NOTE: This section describes how Spring Session provides transparent integration with `HttpSession`. The intent is so that user's can understand what is happening under the covers. This functionality is already integrated and you do NOT need to implement this logic yourself.
First we create a custom `HttpServletRequest` that returns a custom implementation of `HttpSession`.
It looks something like the following:
[source, java]
----
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
public SessionRepositoryRequestWrapper(HttpServletRequest original) {
super(original);
}
public HttpSession getSession() {
return getSession(true);
}
public HttpSession getSession(boolean createNew) {
// create an HttpSession implementation from Spring Session
}
// ... other methods delegate to the original HttpServletRequest ...
}
----
Any method that returns an `HttpSession` is overridden.
All other methods are implemented by `HttpServletRequestWrapper` and simply delegate to the original `HttpServletRequest` implementation.
We replace the `HttpServletRequest` implementation using a servlet `Filter` called `SessionRepositoryFilter`.
The pseudocode can be found below:
[source, java]
----
public class SessionRepositoryFilter implements Filter {
public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
SessionRepositoryRequestWrapper customRequest =
new SessionRepositoryRequestWrapper(httpRequest);
chain.doFilter(customRequest, response, chain);
}
// ...
}
----
By passing in a custom `HttpServletRequest` implementation into the `FilterChain` we ensure that anything invoked after our `Filter` uses the custom `HttpSession` implementation.
This highlights why it is important that Spring Session's `SessionRepositoryFilter` must be placed before anything that interacts with the `HttpSession`.
[[httpsession-multi]]
=== Multiple HttpSessions in Single Browser
Spring Session has the ability to support multiple sessions in a single browser instance.
This provides the ability to support authenticating with multiple users in the same browser instance (i.e. Google Accounts).
NOTE: The <<samples,Manage Multiple Users Guide>> provides a complete working example of managing multiple users in the same browser instance.
You can follow the basic steps for integration below, but you are encouraged to follow along with the detailed Manage Multiple Users Guide when integrating with your own application.
include::guides/users.adoc[tags=how-does-it-work,leveloffset=+1]
[[httpsession-rest]]
=== HttpSession & RESTful APIs
Spring Session can work with RESTful APIs by allowing the session to be provided in a header.
NOTE: The <<samples, REST Sample>> provides a working sample on how to use Spring Session in a REST application to support authenticating with a header.
You can follow the basic steps for integration below, but you are encouraged to follow along with the detailed REST Guide when integrating with your own application.
include::guides/rest.adoc[tags=config,leveloffset=+2]
[[httpsession-httpsessionlistener]]
=== HttpSessionListener
Spring Session supports `HttpSessionListener` by translating `SessionDestroyedEvent` and `SessionCreatedEvent` into `HttpSessionEvent` by declaring `SessionEventHttpSessionListenerAdapter`.
To use this support, you need to:
* Ensure your `SessionRepository` implementation supports and is configured to fire `SessionDestroyedEvent` and `SessionCreatedEvent`.
* Configure `SessionEventHttpSessionListenerAdapter` as a Spring bean.
* Inject every `HttpSessionListener` into the `SessionEventHttpSessionListenerAdapter`
If you are using the configuration support documented in <<httpsession-redis,HttpSession with Redis>>, then all you need to do is register every `HttpSessionListener` as a bean.
For example, assume you want to support Spring Security's concurrency control and need to use `HttpSessionEventPublisher` you can simply add `HttpSessionEventPublisher` as a bean.
In Java configuration, this might look like:
[source,java,indent=0]
----
include::{docs-test-dir}docs/http/RedisHttpSessionConfig.java[tags=config]
----
In XML configuration, this might look like:
[source,xml,indent=0]
----
include::{docs-test-resources-dir}docs/http/HttpSessionListenerXmlTests-context.xml[tags=config]
----
[[websocket]]
== WebSocket Integration
Spring Session provides transparent integration with Spring's WebSocket support.
include::guides/websocket.adoc[tags=disclaimer,leveloffset=+1]
[[websocket-why]]
=== Why Spring Session & WebSockets?
So why do we need Spring Session when using WebSockets?
Consider an email application that does much of its work through HTTP requests.
However, there is also a chat application embedded within it that works over WebSocket APIs.
If a user is actively chatting with someone, we should not timeout the `HttpSession` since this would be pretty poor user experience.
However, this is exactly what https://java.net/jira/browse/WEBSOCKET_SPEC-175[JSR-356] does.
Another issue is that according to JSR-356 if the `HttpSession` times out any WebSocket that was created with that HttpSession and an authenticated user should be forcibly closed.
This means that if we are actively chatting in our application and are not using the HttpSession, then we will also disconnect from our conversation!
[[websocket-usage]]
=== WebSocket Usage
The <<samples, WebSocket Sample>> provides a working sample on how to integrate Spring Session with WebSockets.
You can follow the basic steps for integration below, but you are encouraged to follow along with the detailed WebSocket Guide when integrating with your own application:
[[websocket-httpsession]]
==== HttpSession Integration
Before using WebSocket integration, you should be sure that you have <<httpsession>> working first.
include::guides/websocket.adoc[tags=config,leveloffset=+2]
[[api]]
== API Documentation
You can browse the complete link:../../api/[Javadoc] online. The key APIs are described below:
[[api-session]]
=== Session
A `Session` is a simplified `Map` of name value pairs.
Typical usage might look like the following:
[source,java,indent=0]
----
include::{indexdoc-tests}[tags=repository-demo]
----
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `Session`. The generic type is defined in our class.
<2> We create a new `Session` using our `SessionRepository` and assign it to a variable of type `S`.
<3> We interact with the `Session`. In our example, we demonstrate saving a `User` to the `Session`.
<4> We now save the `Session`. This is why we needed the generic type `S`. The `SessionRepository` only allows saving `Session` instances that were created or retrieved using the same `SessionRepository`. This allows for the `SessionRepository` to make implementation specific optimizations (i.e. only writing attributes that have changed).
<5> We retrieve the `Session` from the `SessionRepository`.
<6> We obtain the persisted `User` from our `Session` without the need for explicitly casting our attribute.
[[api-expiringsession]]
=== ExpiringSession
An `ExpiringSession` extends a `Session` by providing attributes related to the `Session` instance's expiration.
If there is no need to interact with the expiration information, prefer using the more simple `Session` API.
Typical usage might look like the following:
[source,java,indent=0]
----
include::{indexdoc-tests}[tags=expire-repository-demo]
----
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `ExpiringSession`. The generic type is defined in our class.
<2> We create a new `ExpiringSession` using our `SessionRepository` and assign it to a variable of type `S`.
<3> We interact with the `ExpiringSession`.
In our example, we demonstrate updating the amount of time the `ExpiringSession` can be inactive before it expires.
<4> We now save the `ExpiringSession`.
This is why we needed the generic type `S`.
The `SessionRepository` only allows saving `ExpiringSession` instances that were created or retrieved using the same `SessionRepository`.
This allows for the `SessionRepository` to make implementation specific optimizations (i.e. only writing attributes that have changed).
The last accessed time is automatically updated when the `ExpiringSession` is saved.
<5> We retrieve the `ExpiringSession` from the `SessionRepository`.
If the `ExpiringSession` were expired, the result would be null.
[[api-sessionrepository]]
=== SessionRepository
A `SessionRepository` is in charge of creating, retrieving, and persisting `Session` instances.
If possible, developers should not interact directly with a `SessionRepository` or a `Session`.
Instead, developers should prefer interacting with `SessionRepository` and `Session` indirectly through the <<httpsession,HttpSession>> and <<websocket,WebSocket>> integration.
[[api-findbyindexnamesessionrepository]]
=== FindByIndexNameSessionRepository
Spring Session's most basic API for using a `Session` is the `SessionRepository`.
This API is intentionally very simple, so that it is easy to provide additional implementations with basic functionality.
Some `SessionRepository` implementations may choose to implement `FindByIndexNameSessionRepository` also.
For example, Spring's Redis support implements `FindByIndexNameSessionRepository`.
The `FindByIndexNameSessionRepository` adds a single method to look up all the sessions for a particular user.
This is done by ensuring that the session attribute with the name `FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME` is populated with the username.
It is the responsibility of the developer to ensure the attribute is populated since Spring Session is not aware of the authentication mechanism being used.
An example of how this might be used can be seen below:
[source,java,indent=0]
----
include::{docs-test-dir}docs/FindByIndexNameSessionRepositoryTests.java[tags=set-username]
----
[NOTE]
====
Some implementations of `FindByIndexNameSessionRepository` will provide hooks to automatically index other session attributes.
For example, many implementations will automatically ensure the current Spring Security user name is indexed with the index name `FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME`.
====
Once the session is indexed, it can be found using the following:
[source,java,indent=0]
----
include::{docs-test-dir}docs/FindByIndexNameSessionRepositoryTests.java[tags=findby-username]
----
[[api-enablespringhttpsession]]
=== EnableSpringHttpSession
The `@EnableSpringHttpSession` annotation can be added to an `@Configuration` class to expose the `SessionRepositoryFilter` as a bean named "springSessionRepositoryFilter".
In order to leverage the annotation, a single `SessionRepository` bean must be provided.
For example:
[source,java,indent=0]
----
include::{docs-test-dir}docs/SpringHttpSessionConfig.java[tags=class]
----
It is important to note that no infrastructure for session expirations is configured for you out of the box.
This is because things like session expiration are highly implementation dependent.
This means if you require cleaning up expired sessions, you are responsible for cleaning up the expired sessions.
[[api-enablehazelcasthttpsession]]
=== EnableHazelcastHttpSession
If you wish to use http://hazelcast.org/[Hazelcast] as your backing source for the `SessionRepository`, then the `@EnableHazelcastHttpSession` annotation
can be added to an `@Configuration` class. This extends the functionality provided by the `@EnableSpringHttpSession` annotation but makes the `SessionRepository` for you in Hazelcast.
You must provide a single `HazelcastInstance` bean for the configuration to work.
For example:
[source,java,indent=0]
----
include::{docs-test-dir}docs/http/HazelcastHttpSessionConfig.java[tags=config]
----
This will configure Hazelcast in embedded mode with default configuration.
See the http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-configuration[Hazelcast documentation] for
detailed information on configuration options for Hazelcast.
[[api-enablehazelcasthttpsession-storage]]
==== Storage Details
Sessions will be stored in a distributed `Map` in Hazelcast using a <<api-mapsessionrepository,MapSessionRepository>>.
The `Map` interface methods will be used to `get()` and `put()` Sessions.
The expiration of a session in the `Map` is handled by Hazelcast's support for setting the time to live on an entry when it is `put()` into the `Map`. Entries (sessions) that have been idle longer than the time to live will be automatically removed from the `Map`.
You shouldn't need to configure any settings such as `max-idle-seconds` or `time-to-live-seconds` for the `Map` within the Hazelcast configuration.
[[api-enablehazelcasthttpsession-customize]]
==== Basic Customization
You can use the following attributes on `@EnableHazelcastHttpSession` to customize the configuration:
* **maxInactiveIntervalInSeconds** - the amount of time before the session will expire in seconds. Default is 1800 seconds (30 minutes)
* **sessionMapName** - the name of the distributed `Map` that will be used in Hazelcast to store the session data.
[[api-enablehazelcasthttpsession-events]]
==== Session Events
Using a `MapListener` to respond to entries being added, evicted, and removed from the distributed `Map`, these events will trigger
publishing SessionCreatedEvent, SessionExpiredEvent, and SessionDeletedEvent events respectively using the `ApplicationEventPublisher`.
[[api-redisoperationssessionrepository]]
=== RedisOperationsSessionRepository
`RedisOperationsSessionRepository` is a `SessionRepository` that is implemented using Spring Data's `RedisOperations`.
In a web environment, this is typically used in combination with `SessionRepositoryFilter`.
The implementation supports `SessionDestroyedEvent` and `SessionCreatedEvent` through `SessionMessageListener`.
[[api-redisoperationssessionrepository-new]]
==== Instantiating a RedisOperationsSessionRepository
A typical example of how to create a new instance can be seen below:
[source,java,indent=0]
----
include::{indexdoc-tests}[tags=new-redisoperationssessionrepository]
----
For additional information on how to create a `RedisConnectionFactory`, refer to the Spring Data Redis Reference.
[[api-redisoperationssessionrepository-config]]
==== EnableRedisHttpSession
In a web environment, the simplest way to create a new `RedisOperationsSessionRepository` is to use `@EnableRedisHttpSession`.
Complete example usage can be found in the <<samples>>
You can use the following attributes to customize the configuration:
* **maxInactiveIntervalInSeconds** - the amount of time before the session will expire in seconds
* **redisNamespace** - allows configuring an application specific namespace for the sessions. Redis keys and channel ids will start with the prefix of `spring:session:<redisNamespace>:`.
* **redisFlushMode** - allows specifying when data will be written to Redis. The default is only when `save` is invoked on `SessionRepository`.
A value of `RedisFlushMode.IMMEDIATE` will write to Redis as soon as possible.
===== Custom RedisSerializer
You can customize the serialization by creating a Bean named `springSessionDefaultRedisSerializer` that implements `RedisSerializer<Object>`.
==== Redis TaskExecutor
`RedisOperationsSessionRepository` is subscribed to receive events from redis using a `RedisMessageListenerContainer`.
You can customize the way those events are dispatched, by creating a Bean named `springSessionRedisTaskExecutor` and/or a Bean `springSessionRedisSubscriptionExecutor`.
More details on configuring redis task executors can be found http://docs.spring.io/spring-data-redis/docs/current/reference/html/#redis:pubsub:subscribe:containers[here].
[[api-redisoperationssessionrepository-storage]]
==== Storage Details
The sections below outline how Redis is updated for each operation.
An example of creating a new session can be found below.
The subsequent sections describe the details.
----
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr2:attrName someAttrValue2
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100
----
===== Saving a Session
Each session is stored in Redis as a Hash.
Each session is set and updated using the HMSET command.
An example of how each session is stored can be seen below.
----
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr2:attrName someAttrValue2
----
In this example, the session following statements are true about the session:
* The session id is 33fdd1b6-b496-4b33-9f7d-df96679d32fe
* The session was created at 1404360000000 in milliseconds since midnight of 1/1/1970 GMT.
* The session expires in 1800 seconds (30 minutes).
* The session was last accessed at 1404360000000 in milliseconds since midnight of 1/1/1970 GMT.
* The session has two attributes.
The first is "attrName" with the value of "someAttrValue".
The second session attribute is named "attrName2" with the value of "someAttrValue2".
[[api-redisoperationssessionrepository-writes]]
===== Optimized Writes
The `Session` instances managed by `RedisOperationsSessionRepository` keeps track of the properties that have changed and only updates those.
This means if an attribute is written once and read many times we only need to write that attribute once.
For example, assume the session attribute "sessionAttr2" from earlier was updated.
The following would be executed upon saving:
----
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
----
[[api-redisoperationssessionrepository-expiration]]
===== Session Expiration
An expiration is associated to each session using the EXPIRE command based upon the `ExpiringSession.getMaxInactiveInterval()`.
For example:
----
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
----
You will note that the expiration that is set is 5 minutes after the session actually expires.
This is necessary so that the value of the session can be accessed when the session expires.
An expiration is set on the session itself five minutes after it actually expires to ensure it is cleaned up, but only after we perform any necessary processing.
[NOTE]
====
The `SessionRepository.getSession(String)` method ensures that no expired sessions will be returned.
This means there is no need to check the expiration before using a session.
====
Spring Session relies on the delete and expired http://redis.io/topics/notifications[keyspace notifications] from Redis to fire a <<api-redisoperationssessionrepository-sessiondestroyedevent,SessionDeletedEvent>> and <<api-redisoperationssessionrepository-sessiondestroyedevent,SessionExpiredEvent>> respectively.
It is the `SessionDeletedEvent` or `SessionExpiredEvent` that ensures resources associated with the Session are cleaned up.
For example, when using Spring Session's WebSocket support the Redis expired or delete event is what triggers any WebSocket connections associated with the session to be closed.
Expiration is not tracked directly on the session key itself since this would mean the session data would no longer be available. Instead a special session expires key is used. In our example the expires key is:
----
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
----
When a session expires key is deleted or expires, the keyspace notification triggers a lookup of the actual session and a SessionDestroyedEvent is fired.
One problem with relying on Redis expiration exclusively is that Redis makes no guarantee of when the expired event will be fired if they key has not been accessed.
Specifically the background task that Redis uses to clean up expired keys is a low priority task and may not trigger the key expiration.
For additional details see http://redis.io/topics/notifications[Timing of expired events] section in the Redis documentation.
To circumvent the fact that expired events are not guaranteed to happen we can ensure that each key is accessed when it is expected to expire.
This means that if the TTL is expired on the key, Redis will remove the key and fire the expired event when we try to access they key.
For this reason, each session expiration is also tracked to the nearest minute.
This allows a background task to access the potentially expired sessions to ensure that Redis expired events are fired in a more deterministic fashion.
For example:
----
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100
----
The background task will then use these mappings to explicitly request each key.
By accessing they key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
[NOTE]
====
We do not explicitly delete the keys since in some instances there may be a race condition that incorrectly identifies a key as expired when it is not.
Short of using distributed locks (which would kill our performance) there is no way to ensure the consistency of the expiration mapping.
By simply accessing the key, we ensure that the key is only removed if the TTL on that key is expired.
====
[[api-redisoperationssessionrepository-sessiondestroyedevent]]
==== SessionDeletedEvent and SessionExpiredEvent
`SessionDeletedEvent` and `SessionExpiredEvent` are both types of `SessionDestroyedEvent`.
`RedisOperationsSessionRepository` supports firing a `SessionDeletedEvent` whenever a `Session` is deleted or a `SessionExpiredEvent` when it expires.
This is necessary to ensure resources associated with the `Session` are properly cleaned up.
For example, when integrating with WebSockets the `SessionDestroyedEvent` is in charge of closing any active WebSocket connections.
Firing `SessionDeletedEvent` or `SessionExpiredEvent` is made available through the `SessionMessageListener` which listens to http://redis.io/topics/notifications[Redis Keyspace events].
In order for this to work, Redis Keyspace events for Generic commands and Expired events needs to be enabled.
For example:
[source,bash]
----
redis-cli config set notify-keyspace-events Egx
----
If you are using `@EnableRedisHttpSession` the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically.
However, in a secured Redis enviornment the config command is disabled.
This means that Spring Session cannot configure Redis Keyspace events for you.
To disable the automatic configuration add `ConfigureRedisAction.NO_OP` as a bean.
For example, Java Configuration can use the following:
[source,java,indent=0]
----
include::{docs-test-dir}docs/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java[tags=configure-redis-action]
----
XML Configuraiton can use the following:
[source,xml,indent=0]
----
include::{docs-test-resources-dir}docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml[tags=configure-redis-action]
----
[[api-redisoperationssessionrepository-sessioncreatedevent]]
==== SessionCreatedEvent
When a session is created an event is sent to Redis with the channel of `spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe`
such that `33fdd1b6-b496-4b33-9f7d-df96679d32fe` is the session id. The body of the event will be the session that was created.
If registered as a MessageListener (default), then `RedisOperationsSessionRepository` will then translate the Redis message into a `SessionCreatedEvent`.
[[api-redisoperationssessionrepository-cli]]
==== Viewing the Session in Redis
After http://redis.io/topics/quickstart[installing redis-cli], you can inspect the values in Redis http://redis.io/commands#hash[using the redis-cli].
For example, enter the following into a terminal:
[source,bash]
----
$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" <1>
2) "spring:session:expirations:1418772300000" <2>
----
<1> The suffix of this key is the session identifier of the Spring Session.
<2> This key contains all the session ids that should be deleted at the time `1418772300000`.
You can also view the attributes of each session.
[source,bash]
----
redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"
----
[[api-gemfireoperationssessionrepository]]
=== GemFireOperationsSessionRepository
`GemFireOperationsSessionRepository` is a `SessionRepository` that is implemented using Spring Data's `GemFireOperationsSessionRepository`.
In a web environment, this is typically used in combination with `SessionRepositoryFilter`.
The implementation supports `SessionDestroyedEvent` and `SessionCreatedEvent` through `SessionMessageListener`.
[[api-gemfireoperationssessionrepository-indexing]]
==== Using Indexes with GemFire
While best practices concerning the proper definition of indexes that positively impact GemFire's performance is beyond
the scope of this document, it is important to realize that Spring Session Data GemFire creates and uses indexes to
query and find Sessions efficiently.
Out-of-the-box, Spring Session Data GemFire creates 1 Hash-typed Index on the principal name. There are two different buit in
strategies for finding the principal name. The first strategy is that the value of the session attribute with the name
`FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME` will be indexed to the same index name. For example:
[source,java,indent=0]
----
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyindexname-set]
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyindexname-get]
----
[[api-gemfireoperationssessionrepository-indexing-security]]
==== Using Indexes with GemFire & Spring Security
Alternatively, Spring Session Data GemFire will map Spring Security's current `Authentication#getName()` to the index
`FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME`. For example, if you are using Spring Security you can
find the current user's sessions using:
[source,java,indent=0]
----
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyspringsecurityindexname-context]
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyspringsecurityindexname-get]
----
[[api-gemfireoperationssessionrepository-indexing-custom]]
==== Using Custom Indexes with GemFire
This enables developers using the `GemFireOperationsSessionRepository` programmatically to query and find all Sessions
with a given principal name efficiently.
Additionally, Spring Session Data GemFire will create a Range-based Index on the implementing Session's Map-type
`attributes` property (i.e. on any arbitrary Session attribute) when a developer identifies 1 or more named Session
attributes that should be indexed by GemFire.
Sessions attributes to index can be specified with the `indexableSessionAttributes` attribute on the `@EnableGemFireHttpSession`
annotation. A developer adds this annotation to their Spring application `@Configuration` class when s/he wishes to
enable Spring Session support for HttpSession backed by GemFire.
For example, the following configuration:
[source,java,indent=0]
----
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/GemFireHttpSessionConfig.java[tags=class-start]
// ...
}
----
will allow searching for sessions using the following:
[source,java,indent=0]
----
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/HttpSessionGemFireIndexingCustomITests.java[tags=findbyindexname-set]
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/HttpSessionGemFireIndexingCustomITests.java[tags=findbyindexname-get]
----
NOTE: Only Session attribute names identified in the `@EnableGemFireHttpSession` annotation's `indexableSessionAttributes`
attribute will have an Index defined. All other Session attributes will not be indexed.
However, there is one caveat. Any values stored in indexable Session attributes must implement the `java.lang.Comparable<T>`
interface. If those object values do not implement `Comparable`, then GemFire will throw an error on startup when the
Index is defined for Regions with persistent Session data, or when an attempt is made at runtime to assign the indexable
Session attribute a value that is not `Comparable` and the Session is saved to GemFire.
NOTE: Any Session attribute that is not indexed may store non-`Comparable` values.
To learn more about GemFire's Range-based Indexes, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/creating_map_indexes.html[Creating Indexes on Map Fields].
To learn more about GemFire Indexing in general, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index.html[Working with Indexes].
[[api-mapsessionrepository]]
=== MapSessionRepository
The `MapSessionRepository` allows for persisting `ExpiringSession` in a `Map` with the key being the `ExpiringSession` id and the value being the `ExpiringSession`.
The implementation can be used with a `ConcurrentHashMap` as a testing or convenience mechanism.
Alternatively, it can be used with distributed `Map` implementations. For example, it can be used with Hazelcast.
[[api-mapsessionrepository-new]]
==== Instantiating MapSessionRepository
Creating a new instance is as simple as:
[source,java,indent=0]
----
include::{indexdoc-tests}[tags=new-mapsessionrepository]
----
[[api-mapsessionrepository-hazelcast]]
==== Using Spring Session and Hazlecast
The <<samples,Hazelcast Sample>> is a complete application demonstrating using Spring Session with Hazelcast.
To run it use the following:
./gradlew :samples:hazelcast:tomcatRun
The <<samples,Hazelcast Spring Sample>> is a complete application demonstrating using Spring Session with Hazelcast and Spring Security.
It includes example Hazelcast `MapListener` implementations that support firing `SessionCreatedEvent`, `SessionDeletedEvent` and `SessionExpiredEvent`.
To run it use the following:
./gradlew :samples:hazelcast-spring:tomcatRun
[[api-mapsessionrepository-hazelcast]]
==== Using Spring Session and Hazlecast
[[community]]
== Spring Session Community
We are glad to consider you a part of our community.
Please find additional information below.
[[community-support]]
=== Support
You can get help by asking questions on http://stackoverflow.com/questions/tagged/spring-session[StackOverflow with the tag spring-session].
Similarly we encourage helping others by answering questions on StackOverflow.
[[community-source]]
=== Source Code
Our source code can be found on github at https://github.com/spring-projects/spring-session/
[[community-issues]]
=== Issue Tracking
We track issues in github issues at https://github.com/spring-projects/spring-session/issues
[[community-contributing]]
=== Contributing
We appreciate https://help.github.com/articles/using-pull-requests/[Pull Requests].
[[community-license]]
=== License
Spring Session is Open Source software released under the http://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
[[minimum-requirements]]
== Minimum Requirements
The minimum requirements for Spring Session are:
* Java 5+
* If you are running in a Servlet Container (not required), Servlet 2.5+
* If you are using other Spring libraries (not required), the minimum required version is Spring 3.2.14.
While we re-run all unit tests against Spring 3.2.x, we recommend using the latest Spring 4.x version when possible.
* `@EnableRedisHttpSession` requires Redis 2.8+. This is necessary to support <<api-redisoperationssessionrepository-expiration,Session Expiration>>
[NOTE]
====
At its core Spring Session only has a required dependency on commons-logging.
For an example of using Spring Session without any other Spring dependencies, refer to the <<samples,hazelcast sample>> application.
====

View File

@@ -0,0 +1,449 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs;
import static org.assertj.core.api.Assertions.assertThat;
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 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 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;
/**
* AbstractGemFireIntegrationTests is an abstract base class encapsulating common operations for writing
* Spring Session GemFire integration tests.
*
* @author John Blum
* @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
* @since 1.1.0
*/
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) 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) sessionRepository.getSession(sessionId);
}
/* (non-Javadoc) */
protected <T extends ExpiringSession> T save(T session) {
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) {
sessionEvent = event;
}
/* (non-Javadoc) */
public <T extends AbstractSessionEvent> T waitForSessionEvent(long duration) {
waitOnCondition(new Condition() {
public boolean evaluate() {
return (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,120 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Map;
import java.util.Properties;
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 docs.AbstractGemFireIntegrationTests;
/**
* @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.setLazyInitialize(false);
gemfireCache.setProperties(gemfireProperties());
gemfireCache.setUseBeanFactoryLocator(false);
return gemfireCache;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http.gemfire.indexablesessionattributes;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
import docs.AbstractGemFireIntegrationTests;
/**
* @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.setLazyInitialize(false);
gemfireCache.setProperties(gemfireProperties());
gemfireCache.setUseBeanFactoryLocator(false);
return gemfireCache;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http.gemfire.indexablesessionattributes;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Map;
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 docs.AbstractGemFireIntegrationTests;
/**
* @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,11 +1,11 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,16 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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,23 +28,18 @@ 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[]
String username = "username";
this.session.setAttribute(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
// end::set-username[]
}
@@ -55,8 +48,8 @@ public class FindByIndexNameSessionRepositoryTests {
public void findByUsername() {
// tag::findby-username[]
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
.findByPrincipalName(username);
Map<String,Session> sessionIdToSession =
sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
// end::findby-username[]
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,38 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.mock;
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;
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() {
assertThat(this.filter).isNotNull();
assertThat(filter).isNotNull();
}
static RedisConnectionFactory connectionFactory() {
return mock(RedisConnectionFactory.class);
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package docs;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
/**
* @author Rob Winch
*/
public class IndexDocTests {
static final String ATTR_USER = "user";
@Test
public void repositoryDemo() {
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
demo.repository = new MapSessionRepository();
demo.demo();
}
// tag::repository-demo[]
public class RepositoryDemo<S extends Session> {
private SessionRepository<S> repository; // <1>
public void demo() {
S toSave = repository.createSession(); // <2>
// <3>
User rwinch = new User("rwinch");
toSave.setAttribute(ATTR_USER, rwinch);
repository.save(toSave); // <4>
S session = repository.getSession(toSave.getId()); // <5>
// <6>
User user = session.getAttribute(ATTR_USER);
assertThat(user).isEqualTo(rwinch);
}
// ... setter methods ...
}
// end::repository-demo[]
@Test
public void expireRepositoryDemo() {
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
demo.repository = new MapSessionRepository();
demo.demo();
}
@SuppressWarnings("unused")
// tag::expire-repository-demo[]
public class ExpiringRepositoryDemo<S extends ExpiringSession> {
private SessionRepository<S> repository; // <1>
public void demo() {
S toSave = repository.createSession(); // <2>
// ...
toSave.setMaxInactiveIntervalInSeconds(30); // <3>
repository.save(toSave); // <4>
S session = repository.getSession(toSave.getId()); // <5>
// ...
}
// ... setter methods ...
}
// end::expire-repository-demo[]
@Test
@SuppressWarnings("unused")
public void newRedisOperationsSessionRepository() {
// tag::new-redisoperationssessionrepository[]
JedisConnectionFactory factory = new JedisConnectionFactory();
SessionRepository<? extends ExpiringSession> repository =
new RedisOperationsSessionRepository(factory);
// end::new-redisoperationssessionrepository[]
}
@Test
@SuppressWarnings("unused")
public void mapRepository() {
// tag::new-mapsessionrepository[]
SessionRepository<? extends ExpiringSession> repository = new MapSessionRepository();
// end::new-mapsessionrepository[]
}
@Test
public void runSpringHttpSessionConfig() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringHttpSessionConfig.class);
context.setServletContext(new MockServletContext());
context.refresh();
try {
context.getBean(SessionRepositoryFilter.class);
} finally {
context.close();
}
}
private static class User {
private User(String username) {}
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,34 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.Mockito.mock;
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.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;
/**
* @author Rob Winch
*/
@ExtendWith(SpringExtension.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests {
@Test
public void redisConnectionFactoryNotUsedSinceNoValidation() {
}
public void redisConnectionFactoryNotUsedSinceNoValidation() {}
@EnableRedisHttpSession
@Configuration

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 +26,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[]
// end::class[]

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http;
import java.util.Properties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
@@ -29,20 +29,14 @@ 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
@@ -55,40 +49,32 @@ public abstract class AbstractHttpSessionListenerTests {
public void springSessionDestroyedTranslatedToSpringSecurityDestroyed() {
Session session = new MapSession();
this.publisher.publishEvent(
new org.springframework.session.events.SessionDestroyedEvent(this,
session));
publisher.publishEvent(new org.springframework.session.events.SessionDestroyedEvent(this, session));
assertThat(this.listener.getEvent().getId()).isEqualTo(session.getId());
assertThat(listener.getEvent().getId()).isEqualTo(session.getId());
}
static RedisConnectionFactory createMockRedisConnection() {
RedisConnectionFactory factory = mock(RedisConnectionFactory.class);
RedisConnection connection = mock(RedisConnection.class);
given(factory.getConnection()).willReturn(connection);
given(connection.getConfig(anyString())).willReturn(new Properties());
when(factory.getConnection()).thenReturn(connection);
return factory;
}
static class SecuritySessionDestroyedListener
implements ApplicationListener<SessionDestroyedEvent> {
static class SecuritySessionDestroyedListener implements ApplicationListener<SessionDestroyedEvent> {
private SessionDestroyedEvent event;
/*
* (non-Javadoc)
*
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.
* springframework.context.ApplicationEvent)
/* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(SessionDestroyedEvent event) {
this.event = event;
}
public SessionDestroyedEvent getEvent() {
return this.event;
return event;
}
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,41 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapAttributeConfig;
import com.hazelcast.config.MapIndexConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.hazelcast.PrincipalNameExtractor;
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
//tag::config[]
@EnableHazelcastHttpSession // <1>
@Configuration
public class HazelcastHttpSessionConfig {
@Bean
public HazelcastInstance hazelcastInstance() {
MapAttributeConfig attributeConfig = new MapAttributeConfig()
.setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractor(PrincipalNameExtractor.class.getName());
Config config = new Config();
config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
.addMapAttributeConfig(attributeConfig)
.addMapIndexConfig(new MapIndexConfig(
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
return Hazelcast.newHazelcastInstance(config); // <3>
public HazelcastInstance embeddedHazelcast() {
Config hazelcastConfig = new Config();
return Hazelcast.newHazelcastInstance(hazelcastConfig); // <2>
}
}
// end::config[]
// end::config[]

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http;
import org.springframework.context.annotation.Bean;

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http;
import org.springframework.test.context.ContextConfiguration;

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.http;
import org.springframework.context.annotation.Bean;
@@ -33,4 +32,4 @@ public class RedisHttpSessionConfig {
// ...
}
// end::config[]
// end::config[]

View File

@@ -1,40 +1,40 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2002-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
* 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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package docs.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* @author Rob Winch
*/
* @author Rob Winch
*/
// tag::class[]
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public class WebSocketConfig
extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages").withSockJS();
registry.addEndpoint("/messages")
.withSockJS();
}
@Override
@@ -43,4 +43,4 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
registry.setApplicationDestinationPrefixes("/app");
}
}
// end::class[]
// end::class[]

View File

@@ -4,9 +4,9 @@
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util-4.1.xsd">
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<context:annotation-config/>
@@ -19,4 +19,4 @@
<bean class="docs.HttpSessionConfigurationNoOpConfigureRedisActionXmlTests"
factory-method="connectionFactory"/>
</beans>
</beans>

View File

@@ -4,9 +4,9 @@
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util-4.1.xsd">
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<!-- tag::config[] -->
<bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/>
@@ -19,4 +19,4 @@
<bean class="docs.http.AbstractHttpSessionListenerTests"
factory-method="createMockRedisConnection"/>
<bean class="docs.http.AbstractHttpSessionListenerTests$SecuritySessionDestroyedListener"/>
</beans>
</beans>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/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"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<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>
<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"/>
</module>
</module>
</module>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.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"/>
</suppressions>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12">
<profile kind="CodeFormatterProfile" name="Spring Session Java Conventions" version="12">
<profile kind="CodeFormatterProfile" name="Spring" version="12">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
@@ -13,7 +13,7 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
@@ -31,9 +31,9 @@
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
@@ -65,7 +65,7 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="90"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
@@ -79,7 +79,7 @@
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
@@ -133,7 +133,7 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
@@ -161,7 +161,7 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
@@ -175,9 +175,9 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
@@ -202,7 +202,7 @@
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
@@ -266,7 +266,7 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
@@ -283,7 +283,7 @@
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
<local-check-config name="Spring Session Checkstyle" location="${configDir}/checkstyle.xml" type="external" description="">
<property name="configDir" value="${configDir}"/>
<additional-data name="protect-config-file" value="false"/>
</local-check-config>
<fileset name="all" enabled="true" check-config-name="Spring Session Checkstyle" local="true">
<file-match-pattern match-pattern="." include-pattern="true"/>
</fileset>
</fileset-config>

View File

@@ -1,391 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.codeComplete.argumentPrefixes=
org.eclipse.jdt.core.codeComplete.argumentSuffixes=
org.eclipse.jdt.core.codeComplete.fieldPrefixes=
org.eclipse.jdt.core.codeComplete.fieldSuffixes=
org.eclipse.jdt.core.codeComplete.localPrefixes=
org.eclipse.jdt.core.codeComplete.localSuffixes=
org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=default
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=default
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nullReference=ignore
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.processAnnotations=disabled
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=false
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=90
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=90
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=tab
org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.use_on_off_tags=true
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
org.eclipse.jdt.core.javaFormatter=org.springframework.ide.eclipse.jdt.formatter.javaformatter

File diff suppressed because one or more lines are too long

View File

@@ -1 +1,22 @@
version=2.2.0.M2
commonsPoolVersion=2.4.2
jacksonVersion=2.6.5
jspApiVersion=2.0
servletApiVersion=3.0.1
jstlelVersion=1.2.5
version=1.1.2.BUILD-SNAPSHOT
springDataRedisVersion=1.6.2.RELEASE
junitVersion=4.12
gebVersion=0.13.1
mockitoVersion=1.10.19
hazelcastVersion=3.5.4
seleniumVersion=2.52.0
springSecurityVersion=4.0.3.RELEASE
springVersion=4.2.5.RELEASE
httpClientVersion=4.5.1
jedisVersion=2.7.3
springShellVersion=1.1.0.RELEASE
springDataGemFireVersion=1.7.2.RELEASE
assertjVersion=2.3.0
spockVersion=1.0-groovy-2.4
jstlVersion=1.2.1
groovyVersion=2.4.4

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'
}
}

86
gradle/java.gradle Normal file
View File

@@ -0,0 +1,86 @@
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'
group = 'org.springframework.session'
sourceCompatibility = 1.5
targetCompatibility = 1.5
ext.springIoVersion = project.hasProperty('platformVersion') ? platformVersion : 'latest.integration'
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
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 = "http://projects.spring.io/spring-session"
}
licenses {
license {
name "The Apache Software License, Version 2.0"
url "http://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"
}
}
}
}

24
gradle/spring3.gradle Normal file
View File

@@ -0,0 +1,24 @@
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'
}
}
}
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

64
gradle/tomcat.gradle Normal file
View File

@@ -0,0 +1,64 @@
buildscript {
repositories {
maven { url "https://repo.spring.io/plugins-release" }
}
dependencies {
classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3")
}
}
apply plugin: 'war'
apply plugin: 'tomcat'
[tomcatRun,tomcatRunWar]*.contextPath = '/'
task integrationTomcatRun(type: org.gradle.api.plugins.tomcat.tasks.TomcatRun) {
onlyIf { !sourceSets.integrationTest.allSource.empty }
buildscriptClasspath = tomcatRun.buildscriptClasspath
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: org.gradle.api.plugins.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}"
}

10
gradle/tomcat7.gradle Normal file
View File

@@ -0,0 +1,10 @@
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}"
tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
}
}

Binary file not shown.

View File

@@ -1,5 +1,6 @@
#Tue Nov 25 20:57:10 CST 2014
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-2.10-bin.zip

126
gradlew vendored
View File

@@ -1,20 +1,4 @@
#!/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.
#
#!/usr/bin/env bash
##############################################################################
##
@@ -22,6 +6,47 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS="-Xmx1024M -XX:MaxPermSize=512M"
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -36,49 +61,9 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -105,7 +90,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -129,7 +114,6 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -170,19 +154,11 @@ if $cygwin ; then
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
APP_ARGS=$(save "$@")
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

30
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
@@ -24,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@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=-Xmx1024M -XX:MaxPermSize=512M
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
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"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -62,9 +46,10 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -75,6 +60,11 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line

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".

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

@@ -0,0 +1,53 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
}
}
apply plugin: 'spring-boot'
apply from: JAVA_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false
group = 'samples'
dependencies {
compile project(':spring-session'),
"org.springframework.boot:spring-boot-starter-redis",
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-thymeleaf",
"nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect",
"org.springframework.security:spring-security-web:$springSecurityVersion",
"org.springframework.security:spring-security-config:$springSecurityVersion"
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,74 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.pages
package sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import geb.*
/**
* The home page
*
* @author Rob Winch
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
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,11 +1,11 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,19 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.pages
package sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import geb.*
/**
* The Links Page
*
* @author Rob Winch
*/
@SpringBootApplication
public class FindByUsernameApplication {
public static void main(String[] args) {
SpringApplication.run(FindByUsernameApplication.class, args);
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)
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author Rob Winch
*/
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.config;
/**
* Spring Session reactive web support.
*/
@NonNullApi
package org.springframework.session.web.server.session;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.lang.NonNullApi;
// tag::class[]
@EnableRedisHttpSession // <1>
public class HttpSessionConfig { }
// end::class[]

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package sample.config;
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;
/**
* @author Rob Winch
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
<head>
<title>Secured Content</title>
</head>
@@ -8,4 +8,4 @@
<p>This page is secured using Spring Boot, Spring Session, and Spring Security.</p>
</div>
</body>
</html>
</html>

View File

@@ -1,11 +1,11 @@
<!DOCTYPE html SYSTEM "https://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd">
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="https://www.thymeleaf.org"
xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect">
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<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"/>
<link th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"></link>
<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="@{/resources/css/bootstrap.css}" href="../static/css/bootstrap.css" rel="stylesheet"></link>
<style type="text/css">
/* Sticky footer styles
-------------------------------------------------- */
@@ -65,11 +65,11 @@
margin-left: 1em;
}
</style>
<link th:href="@{/webjars/bootstrap/css/bootstrap-responsive.min.css}" href="/webjars/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet"></link>
<link th:href="@{resources/css/bootstrap-responsive.css}" href="/static/css/bootstrap-responsive.css" rel="stylesheet"></link>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script th:src="@{/webjars/html5shiv/html5shiv.min.js}" src="/webjars/html5shiv/html5shiv.min.js"></script>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
@@ -79,10 +79,10 @@
<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}">
th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}">
<div th:if="${currentUser != null}">
<form class="navbar-form pull-right" th:action="@{/logout}" method="post">
<input type="submit" value="Log out" />
@@ -115,8 +115,8 @@
<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="http://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>
</html>
</html>

View File

@@ -0,0 +1,19 @@
apply from: JAVA_GRADLE
apply from: TOMCAT_7_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner {
skipProject = true
}
dependencies {
compile project(':spring-session-data-redis'),
"org.springframework:spring-web:$springVersion",
jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile "junit:junit:$junitVersion"
integrationTestCompile gebDependencies
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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
*/
@Stepwise
class AttributeTests extends GebReportingSpec {
def 'first visit no attributes'() {
when:
to HomePage
then:
attributes.empty
}
def 'create attribute'() {
when:
createAttribute('a','b')
then:
attributes.size() == 1
attributes[0].name == 'a'
attributes[0].value == 'b'
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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,11 +1,11 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,23 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.data.redis.connection.jedis.JedisConnectionFactory;
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 {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
// tag::cookie-serializer[]
@@ -42,4 +40,4 @@ public class Config {
return serializer;
}
// end::cookie-serializer[]
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -18,10 +18,11 @@ package sample;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
// tag::class[]
public class Initializer extends AbstractHttpSessionApplicationInitializer { // <1>
public class Initializer
extends AbstractHttpSessionApplicationInitializer { // <1>
public Initializer() {
super(Config.class); // <2>
}
}
// end::class[]
// end::class[]

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,22 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// tag::class[]
@WebServlet("/session")
public class SessionServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String attributeName = req.getParameter("attributeName");
String attributeValue = req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue);
@@ -37,5 +34,4 @@ public class SessionServlet extends HttpServlet {
private static final long serialVersionUID = 2878267318695777395L;
}
// end::class[]
// tag::end[]

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,9 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Session Attributes</title>
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
<link rel="stylesheet" href="assets/bootstrap.min.css">
<style type="text/css">
body {
padding: 1em;

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 http://www.maxmind.com

View File

@@ -0,0 +1,56 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
}
}
apply plugin: 'spring-boot'
apply from: JAVA_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false
group = 'samples'
dependencies {
compile project(':spring-session'),
"org.springframework.boot:spring-boot-starter-redis",
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-thymeleaf",
"nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect",
"org.springframework.security:spring-security-web:$springSecurityVersion",
"org.springframework.security:spring-security-config:$springSecurityVersion",
"com.maxmind.geoip2:geoip2:2.3.1",
"org.apache.httpcomponents:httpclient"
testCompile "org.springframework.boot:spring-boot-starter-test",
"org.assertj:assertj-core:$assertjVersion"
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,104 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample
import geb.Browser
import geb.spock.*
import org.openqa.selenium.htmlunit.HtmlUnitDriver
import org.springframework.boot.test.IntegrationTest
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.web.WebAppConfiguration
import pages.*
import sample.pages.HomePage
import sample.pages.LoginPage
import spock.lang.Stepwise
/**
* Tests findbyusername web application
*
* @author Rob Winch
*/
@Stepwise
@ContextConfiguration(classes = FindByUsernameApplication, loader = SpringApplicationContextLoader)
@WebAppConfiguration
@IntegrationTest
class FindByUsernameTests extends MultiBrowserGebSpec {
def 'Unauthenticated user sent to log in page'() {
given: 'unauthenticated user request protected page'
withBrowserSession a, {
via HomePage
then: 'sent to the log in page'
at LoginPage
}
}
def 'Log in views home page'() {
when: 'log in successfully'
a.login()
then: 'sent to original page'
a.at HomePage
and: 'the username is displayed'
a.username == 'user'
and: 'Spring Session Management is being used'
a.driver.manage().cookies.find { it.name == 'SESSION' }
and: 'Standard Session is NOT being used'
!a.driver.manage().cookies.find { it.name == 'JSESSIONID' }
and: 'Session id exists and terminate disabled'
a.sessionId
a.terminate(a.sessionId).@disabled
}
def 'Other Browser: Unauthenticated user sent to log in page'() {
given:
withBrowserSession b, {
via HomePage
then: 'sent to the log in page'
at LoginPage
}
}
def 'Other Browser: Log in views home page'() {
when: 'log in successfully'
b.login()
then: 'sent to original page'
b.at HomePage
and: 'the username is displayed'
b.username == 'user'
and: 'Spring Session Management is being used'
b.driver.manage().cookies.find { it.name == 'SESSION' }
and: 'Standard Session is NOT being used'
!b.driver.manage().cookies.find { it.name == 'JSESSIONID' }
and: 'Session id exists and terminate disabled'
b.sessionId
b.terminate(b.sessionId).@disabled
}
def 'Other Browser: Terminate Original'() {
setup: 'get the session id from browser a'
def sessionId = a.sessionId
when: 'we terminate browser a session'
b.terminate(sessionId).click(HomePage)
then: 'session is not listed'
!b.terminate(sessionId)
when: 'browser a visits home page after session was terminated'
a.via HomePage
then: 'browser a sent to log in'
a.at LoginPage
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import geb.*
import spock.lang.*
/**
* https://github.com/kensiprell/geb-multibrowser
*/
abstract class MultiBrowserGebSpec extends Specification {
String gebConfEnv = null
String gebConfScript = null
// Map of geb browsers which can be referenced by name in the spec
// THese currently share the same config. This is not a problem for
// my uses, but I can see potential for wanting to configure different
// browsers separately
@Shared _browsers = createBrowserMap()
def currentBrowser
def createBrowserMap() {
[:].withDefault { new Browser(createConf()) }
}
Configuration createConf() {
// Use the standard configured geb driver, but turn off cacheing so
// we can run multiple
def conf = new ConfigurationLoader(gebConfEnv).getConf(gebConfScript)
conf.cacheDriver = false
return conf
}
def withBrowserSession(browser, Closure c) {
currentBrowser = browser
def returnedValue = c.call()
currentBrowser = null
returnedValue
}
void resetBrowsers() {
_browsers.each { k, browser ->
if (browser.config?.autoClearCookies) {
browser.clearCookiesQuietly()
}
browser.quit()
}
_browsers = createBrowserMap()
}
def propertyMissing(String name) {
if(currentBrowser) {
return currentBrowser."$name"
} else {
return _browsers[name]
}
}
def methodMissing(String name, args) {
if(currentBrowser) {
return currentBrowser."$name"(*args)
} else {
def browser = _browsers[name]
if(args) {
return browser."${args[0]}"(*(args[1..-1]))
} else {
return browser
}
}
}
def propertyMissing(String name, value) {
if(!currentBrowser) throw new IllegalArgumentException("No context for setting property $name")
currentBrowser."$name" = value
}
private isSpecStepwise() {
this.class.getAnnotation(Stepwise) != null
}
def cleanup() {
if (!isSpecStepwise()) resetBrowsers()
}
def cleanupSpec() {
if (isSpecStepwise()) resetBrowsers()
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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() }
sessions { moduleList AttributeRow, $("table tr").tail() }
terminate(required:false) { sessionId -> $("#terminate-$sessionId") }
sessionId { $("#session-id").text() }
}
}
class AttributeRow extends Module {
static content = {
cell { $("td", it) }
location { cell(0).text() }
created { cell(1).text() }
lastUpdated { cell(2).text() }
information { cell(3).text() }
terminate { cell(4).text() }
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.pages
import geb.*
/**
* The Links Page
*
* @author Rob Winch
*/
class LoginPage extends Page {
static url = '/login'
static at = { assert driver.title == 'Spring Session Sample - Log In'; true}
static content = {
form { $('form') }
submit { $('button[type=submit]') }
login(required:false) { user='user', pass='password' ->
form.username = user
form.password = pass
submit.click(HomePage)
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author Rob Winch
*/
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class FindByUsernameApplication {
public static void main(String[] args) {
SpringApplication.run(FindByUsernameApplication.class, args);
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,17 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.config;
import java.io.InputStream;
import com.maxmind.geoip2.DatabaseReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.maxmind.geoip2.DatabaseReader;
/**
*
* @author Rob Winch
@@ -33,9 +32,7 @@ import org.springframework.context.annotation.Configuration;
public class GeoConfig {
@Bean
public DatabaseReader geoDatabaseReader(
@Value("classpath:GeoLite2-City.mmdb") InputStream geoInputStream)
throws Exception {
public DatabaseReader geoDatabaseReader(@Value("classpath:GeoLite2-City.mmdb") InputStream geoInputStream) throws Exception {
return new DatabaseReader.Builder(geoInputStream).build();
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.config;
package sample;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
/**
*
* @author Rob Winch
*
*/
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
// tag::class[]
@EnableRedisHttpSession // <1>
public class HttpSessionConfig { }
// end::class[]

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package sample.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author Rob Winch
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// tag::config[]
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.permitAll();
}
// end::config[]
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}

View File

@@ -1,19 +1,18 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2002-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
* 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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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 java.security.Principal;
@@ -21,8 +20,8 @@ import java.util.Collection;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
@@ -39,26 +38,26 @@ import org.springframework.web.bind.annotation.RequestMethod;
public class IndexController {
// tag::findbyusername[]
@Autowired
FindByIndexNameSessionRepository<? extends Session> sessions;
FindByIndexNameSessionRepository<? extends ExpiringSession> sessions;
@RequestMapping("/")
public String index(Principal principal, Model model) {
Collection<? extends Session> usersSessions = this.sessions
.findByPrincipalName(principal.getName()).values();
Collection<? extends ExpiringSession> usersSessions =
sessions.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName()).values();
model.addAttribute("sessions", usersSessions);
return "index";
}
// end::findbyusername[]
@RequestMapping(value = "/sessions/{sessionIdToDelete}", method = RequestMethod.DELETE)
public String removeSession(Principal principal,
@PathVariable String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions
.findByPrincipalName(principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {
this.sessions.deleteById(sessionIdToDelete);
public String removeSession(Principal principal, @PathVariable String sessionIdToDelete) {
Set<String> usersSessionIds = sessions.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName()).keySet();
if(usersSessionIds.contains(sessionIdToDelete)) {
sessions.delete(sessionIdToDelete);
}
return "redirect:/";
}
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,18 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.mvc;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CsrfController {
/**
* Returns view for log in page
*
* @author Rob Winch
*/
@Controller
public class LoginController {
@RequestMapping("/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
@RequestMapping("/login")
public String login() {
return "login";
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2002-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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.session;
import java.io.Serializable;
@@ -31,7 +30,7 @@ public class SessionDetails implements Serializable {
private String accessType;
public String getLocation() {
return this.location;
return location;
}
public void setLocation(String location) {
@@ -39,7 +38,7 @@ public class SessionDetails implements Serializable {
}
public String getAccessType() {
return this.accessType;
return accessType;
}
public void setAccessType(String accessType) {

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