Spaces to tabs and license cleanup

This commit is contained in:
Rob Winch
2015-04-02 15:57:00 -05:00
parent 93b8856a20
commit 4dedb4d10a
143 changed files with 8270 additions and 7654 deletions

View File

@@ -1,14 +1,14 @@
buildscript { buildscript {
repositories { repositories {
maven { url "https://repo.spring.io/plugins-release" } maven { url "https://repo.spring.io/plugins-release" }
} }
dependencies { dependencies {
classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3") classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3")
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7") classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7")
classpath("org.springframework.build.gradle:spring-io-plugin:0.0.3.RELEASE") classpath("org.springframework.build.gradle:spring-io-plugin:0.0.3.RELEASE")
classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1') classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1')
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2' classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2'
} }
} }
group = 'org.springframework.session' group = 'org.springframework.session'
@@ -27,37 +27,37 @@ apply plugin: 'base'
sonarRunner { sonarRunner {
sonarProperties { sonarProperties {
property "sonar.java.coveragePlugin", "jacoco" property "sonar.java.coveragePlugin", "jacoco"
property "sonar.projectName", "Spring Session" property "sonar.projectName", "Spring Session"
property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec" property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
property "sonar.links.homepage", 'https://github.com/spring-projects/spring-session' property "sonar.links.homepage", 'https://github.com/spring-projects/spring-session'
property "sonar.links.ci", 'https://build.spring.io/browse/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.issue", 'https://github.com/spring-projects/spring-session/issues'
property "sonar.links.scm", 'https://github.com/spring-projects/spring-session' 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.links.scm_dev", 'https://github.com/spring-projects/spring-session.git'
property "sonar.java.coveragePlugin", "jacoco" property "sonar.java.coveragePlugin", "jacoco"
} }
} }
task configDocsZip(dependsOn: [':docs:asciidoctor',':spring-session:javadoc']) << { task configDocsZip(dependsOn: [':docs:asciidoctor',':spring-session:javadoc']) << {
project.tasks.docsZip.from(project(':docs').asciidoctor) { project.tasks.docsZip.from(project(':docs').asciidoctor) {
into('reference') into('reference')
} }
project.tasks.docsZip.from(project(':spring-session').javadoc) { project.tasks.docsZip.from(project(':spring-session').javadoc) {
into('api') into('api')
} }
} }
task docsZip(type: Zip, dependsOn: 'configDocsZip') { task docsZip(type: Zip, dependsOn: 'configDocsZip') {
group = "Distribution" group = "Distribution"
baseName = "spring-session" baseName = "spring-session"
classifier = "docs" classifier = "docs"
description = "Builds -${classifier} archive containing api and reference " + description = "Builds -${classifier} archive containing api and reference " +
"for deployment." "for deployment."
} }
artifacts { artifacts {
archives docsZip archives docsZip
} }

View File

@@ -8,37 +8,37 @@ asciidoctorj {
tasks.findByPath("artifactoryPublish")?.enabled = false tasks.findByPath("artifactoryPublish")?.enabled = false
dependencies { dependencies {
testCompile project(':spring-session'), testCompile project(':spring-session'),
"org.springframework.data:spring-data-redis:$springDataRedisVersion", "org.springframework.data:spring-data-redis:$springDataRedisVersion",
"org.springframework:spring-websocket:${springVersion}", "org.springframework:spring-websocket:${springVersion}",
"org.springframework:spring-messaging:${springVersion}", "org.springframework:spring-messaging:${springVersion}",
'junit:junit:4.11', 'junit:junit:4.11',
'org.mockito:mockito-core:1.9.5', 'org.mockito:mockito-core:1.9.5',
"org.springframework:spring-test:$springVersion", "org.springframework:spring-test:$springVersion",
'org.easytesting:fest-assert:1.4', 'org.easytesting:fest-assert:1.4',
"redis.clients:jedis:2.4.1" "redis.clients:jedis:2.4.1"
} }
asciidoctor { asciidoctor {
def ghTag = snapshotBuild ? 'master' : project.version def ghTag = snapshotBuild ? 'master' : project.version
def ghUrl = "https://github.com/spring-projects/spring-session/tree/$ghTag/" def ghUrl = "https://github.com/spring-projects/spring-session/tree/$ghTag/"
attributes 'version-snapshot': snapshotBuild, attributes 'version-snapshot': snapshotBuild,
'version-milestone': milestoneBuild, 'version-milestone': milestoneBuild,
'version-release': releaseBuild, 'version-release': releaseBuild,
'gh-url': ghUrl, 'gh-url': ghUrl,
'gh-samples-url': "$ghUrl/samples/", 'gh-samples-url': "$ghUrl/samples/",
'download-url' : "https://github.com/spring-projects/spring-session/archive/${ghTag}.zip", 'download-url' : "https://github.com/spring-projects/spring-session/archive/${ghTag}.zip",
'spring-session-version' : version, 'spring-session-version' : version,
'spring-version' : springVersion, 'spring-version' : springVersion,
'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/', 'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/',
'samples-dir' : rootProject.projectDir.path + '/samples/', 'samples-dir' : rootProject.projectDir.path + '/samples/',
'source-highlighter' : 'coderay', 'source-highlighter' : 'coderay',
'imagesdir':'./images', 'imagesdir':'./images',
'icons': 'font', 'icons': 'font',
'sectanchors':'', 'sectanchors':'',
'idprefix':'', 'idprefix':'',
'idseparator':'-', 'idseparator':'-',
'docinfo1':'true', 'docinfo1':'true',
'revnumber' : project.version 'revnumber' : project.version
} }

View File

@@ -16,14 +16,14 @@ If you are using Maven, ensure to add the following dependencies:
[subs="verbatim,attributes"] [subs="verbatim,attributes"]
---- ----
<dependencies> <dependencies>
<!-- ... --> <!-- ... -->
<dependency> <dependency>
<groupId>org.springframework.session</groupId> <groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId> <artifactId>spring-session-data-redis</artifactId>
<version>{spring-session-version}</version> <version>{spring-session-version}</version>
<type>pom<type> <type>pom<type>
</dependency> </dependency>
</dependencies> </dependencies>
---- ----
@@ -36,12 +36,12 @@ Ensure you have the following in your pom.xml:
---- ----
<repositories> <repositories>
<!-- ... --> <!-- ... -->
<repository> <repository>
<id>spring-snapshot</id> <id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url> <url>https://repo.spring.io/libs-snapshot</url>
</repository> </repository>
</repositories> </repositories>
---- ----
endif::[] endif::[]
@@ -54,8 +54,8 @@ Ensure you have the following in your pom.xml:
[source,xml] [source,xml]
---- ----
<repository> <repository>
<id>spring-milestone</id> <id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url> <url>https://repo.spring.io/libs-milestone</url>
</repository> </repository>
---- ----
endif::[] endif::[]
@@ -69,7 +69,7 @@ Add the following Spring Configuration:
[source,java] [source,java]
---- ----
include::{samples-dir}boot/src/main/java/sample/config/HttpSessionConfig.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. <1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
@@ -111,7 +111,9 @@ The boot Sample Application demonstrates how to use Spring Session to transparen
You can run the sample by obtaining the {download-url}[source code] and invoking the following command: You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
$ ./gradlew :samples:boot:bootRun ----
$ ./gradlew :samples:boot:bootRun
----
You should now be able to access the application at http://localhost:8080/ You should now be able to access the application at http://localhost:8080/
@@ -139,12 +141,12 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type: 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 $ redis-cli keys '*' | xargs redis-cli del
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli]. 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: 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 $ 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. Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

View File

@@ -15,19 +15,19 @@ If you are using Maven, ensure to add the following dependencies:
[subs="verbatim,attributes"] [subs="verbatim,attributes"]
---- ----
<dependencies> <dependencies>
<!-- ... --> <!-- ... -->
<dependency> <dependency>
<groupId>org.springframework.session</groupId> <groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId> <artifactId>spring-session-data-redis</artifactId>
<version>{spring-session-version}</version> <version>{spring-session-version}</version>
<type>pom<type> <type>pom<type>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>
<version>{spring-version}</version> <version>{spring-version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
---- ----
@@ -40,12 +40,12 @@ Ensure you have the following in your pom.xml:
---- ----
<repositories> <repositories>
<!-- ... --> <!-- ... -->
<repository> <repository>
<id>spring-snapshot</id> <id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url> <url>https://repo.spring.io/libs-snapshot</url>
</repository> </repository>
</repositories> </repositories>
---- ----
endif::[] endif::[]
@@ -58,8 +58,8 @@ Ensure you have the following in your pom.xml:
[source,xml] [source,xml]
---- ----
<repository> <repository>
<id>spring-milestone</id> <id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url> <url>https://repo.spring.io/libs-milestone</url>
</repository> </repository>
---- ----
endif::[] endif::[]
@@ -75,7 +75,7 @@ Add the following Spring Configuration:
[source,java] [source,java]
---- ----
include::{samples-dir}httpsession/src/main/java/sample/Config.java[] include::{samples-dir}httpsession/src/main/java/sample/Config.java[tags=class]
---- ----
<1> We import an embedded Redis Server so that there is no need to start up Redis external of our application. <1> We import an embedded Redis Server so that there is no need to start up Redis external of our application.
@@ -100,7 +100,7 @@ You can find an example below:
.src/main/java/sample/Initializer.java .src/main/java/sample/Initializer.java
[source,java] [source,java]
---- ----
include::{samples-dir}httpsession/src/main/java/sample/Initializer.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`. NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
@@ -120,7 +120,9 @@ This ensures that the Spring Bean by the name `springSessionRepositoryFilter` is
You can run the sample by obtaining the {download-url}[source code] and invoking the following command: You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
$ ./gradlew :samples:httpsession:tomcatRun ----
$ ./gradlew :samples:httpsession:tomcatRun
----
You should now be able to access the application at http://localhost:8080/ You should now be able to access the application at http://localhost:8080/
@@ -140,7 +142,7 @@ We interact with the standard `HttpSession` in the `SessionServlet` shown below:
.src/main/java/sample/SessionServlet.java .src/main/java/sample/SessionServlet.java
[source,java] [source,java]
---- ----
include::{samples-dir}httpsession/src/main/java/sample/SessionServlet.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. Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
@@ -149,12 +151,12 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type: 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 $ redis-cli keys '*' | xargs redis-cli del
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli]. 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: 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 $ 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. Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.

View File

@@ -15,19 +15,19 @@ If you are using Maven, ensure to add the following dependencies:
[subs="verbatim,attributes"] [subs="verbatim,attributes"]
---- ----
<dependencies> <dependencies>
<!-- ... --> <!-- ... -->
<dependency> <dependency>
<groupId>org.springframework.session</groupId> <groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId> <artifactId>spring-session-data-redis</artifactId>
<version>{spring-session-version}</version> <version>{spring-session-version}</version>
<type>pom<type> <type>pom<type>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>
<version>{spring-version}</version> <version>{spring-version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
---- ----
@@ -40,12 +40,12 @@ Ensure you have the following in your pom.xml:
---- ----
<repositories> <repositories>
<!-- ... --> <!-- ... -->
<repository> <repository>
<id>spring-snapshot</id> <id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url> <url>https://repo.spring.io/libs-snapshot</url>
</repository> </repository>
</repositories> </repositories>
---- ----
endif::[] endif::[]
@@ -58,8 +58,8 @@ Ensure you have the following in your pom.xml:
[source,xml] [source,xml]
---- ----
<repository> <repository>
<id>spring-milestone</id> <id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url> <url>https://repo.spring.io/libs-milestone</url>
</repository> </repository>
---- ----
endif::[] endif::[]
@@ -75,7 +75,7 @@ Add the following Spring Configuration:
[source,java] [source,java]
---- ----
include::{samples-dir}rest/src/main/java/sample/HttpSessionConfig.java[] include::{samples-dir}rest/src/main/java/sample/HttpSessionConfig.java[tags=class]
---- ----
<1> We import an embedded Redis Server so that there is no need to start up Redis external of our application. <1> We import an embedded Redis Server so that there is no need to start up Redis external of our application.
@@ -107,7 +107,7 @@ Fortunately, Spring Session provides a utility class named `AbstractHttpSessionA
.src/main/java/sample/Initializer.java .src/main/java/sample/Initializer.java
[source,java] [source,java]
---- ----
include::{samples-dir}rest/src/main/java/sample/Initializer.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`. NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
@@ -121,7 +121,9 @@ NOTE: The name of our class (Initializer) does not matter. What is important is
You can run the sample by obtaining the {download-url}[source code] and invoking the following command: You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
$ ./gradlew :samples:rest:tomcatRun ----
$ ./gradlew :samples:rest:tomcatRun
----
You should now be able to access the application at http://localhost:8080/ You should now be able to access the application at http://localhost:8080/
@@ -129,14 +131,14 @@ You should now be able to access the application at http://localhost:8080/
Try using the application. Use your favorite REST client to request http://localhost:8080/ Try using the application. Use your favorite REST client to request http://localhost:8080/
$ curl -v 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: Observe that we are prompted for basic authentication. Provide the following information for the username and password:
* **Username** *user* * **Username** *user*
* **Password** *password* * **Password** *password*
$ curl -v http://localhost:8080/ -u user:password $ curl -v http://localhost:8080/ -u user:password
In the output you will notice the following: In the output you will notice the following:
@@ -156,13 +158,13 @@ Specifically, we notice the following things about our response:
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: 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" $ 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. 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: 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" $ 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. You will see in the output that the x-auth-token provides an empty String indicating that the previous session was invalidated.
@@ -181,7 +183,7 @@ Spring Session creates a header named x-auth-token in your browser that contains
If you like, you can easily see that the session is created in Redis. First create a session using the following: 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 $ curl -v http://localhost:8080/ -u user:password
In the output you will notice the following: In the output you will notice the following:
@@ -195,14 +197,14 @@ x-auth-token: 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now remove the session using redis-cli. For example, on a Linux based system you can type: Now remove the session using redis-cli. For example, on a Linux based system you can type:
$ redis-cli keys '*' | xargs redis-cli del $ redis-cli keys '*' | xargs redis-cli del
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli]. 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: 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 $ 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: 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" $ curl -v http://localhost:8080/ -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"

View File

@@ -16,19 +16,19 @@ If you are using Maven, ensure to add the following dependencies:
[subs="verbatim,attributes"] [subs="verbatim,attributes"]
---- ----
<dependencies> <dependencies>
<!-- ... --> <!-- ... -->
<dependency> <dependency>
<groupId>org.springframework.session</groupId> <groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId> <artifactId>spring-session-data-redis</artifactId>
<version>{spring-session-version}</version> <version>{spring-session-version}</version>
<type>pom<type> <type>pom<type>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>
<version>{spring-version}</version> <version>{spring-version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
---- ----
@@ -41,12 +41,12 @@ Ensure you have the following in your pom.xml:
---- ----
<repositories> <repositories>
<!-- ... --> <!-- ... -->
<repository> <repository>
<id>spring-snapshot</id> <id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url> <url>https://repo.spring.io/libs-snapshot</url>
</repository> </repository>
</repositories> </repositories>
---- ----
endif::[] endif::[]
@@ -59,8 +59,8 @@ Ensure you have the following in your pom.xml:
[source,xml] [source,xml]
---- ----
<repository> <repository>
<id>spring-milestone</id> <id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url> <url>https://repo.spring.io/libs-milestone</url>
</repository> </repository>
---- ----
endif::[] endif::[]
@@ -74,7 +74,7 @@ Add the following Spring Configuration:
[source,java] [source,java]
---- ----
include::{samples-dir}security/src/main/java/sample/Config.java[] include::{samples-dir}security/src/main/java/sample/Config.java[tags=class]
---- ----
<1> We import an embedded Redis Server so that there is no need to start up Redis external of our application. <1> We import an embedded Redis Server so that there is no need to start up Redis external of our application.
@@ -97,7 +97,7 @@ Since our application is already loading Spring configuration using our `Securit
.src/main/java/sample/SecurityInitializer.java .src/main/java/sample/SecurityInitializer.java
[source,java] [source,java]
---- ----
include::{samples-dir}security/src/main/java/sample/SecurityInitializer.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. Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
@@ -109,7 +109,7 @@ You can find an example below:
.src/main/java/sample/Initializer.java .src/main/java/sample/Initializer.java
[source,java] [source,java]
---- ----
include::{samples-dir}security/src/main/java/sample/Initializer.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`. NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
@@ -125,7 +125,9 @@ By extending `AbstractHttpSessionApplicationInitializer` we ensure that the Spri
You can run the sample by obtaining the {download-url}[source code] and invoking the following command: You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
$ ./gradlew :samples:security:tomcatRun ----
$ ./gradlew :samples:security:tomcatRun
----
You should now be able to access the application at http://localhost:8080/ You should now be able to access the application at http://localhost:8080/
@@ -151,12 +153,12 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type: 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 $ redis-cli keys '*' | xargs redis-cli del
TIP: The Redis documentation has instructions for http://redis.io/topics/quickstart[installing redis-cli]. 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: 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 $ 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. Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

View File

@@ -17,7 +17,9 @@ The users application demonstrates how to allow an application to manage multipl
You can run the sample by obtaining the {download-url}[source code] and invoking the following command: You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
$ ./gradlew :samples:users:tomcatRun ----
$ ./gradlew :samples:users:tomcatRun
----
You should now be able to access the application at http://localhost:8080/ You should now be able to access the application at http://localhost:8080/
@@ -71,7 +73,7 @@ Let's take a look at how Spring Session keeps track of multiple sessions.
Spring Session keeps track of the `HttpSession` by adding a value to a cookie named 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: For example, the SESSION cookie might have a value of:
7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
=== Adding a Session === Adding a Session
@@ -113,7 +115,7 @@ The URL contains a session alias that either points to an existing unauthenticat
Now our SESSION cookie looks something like this: Now our SESSION cookie looks something like this:
0 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 1 1d526d4a-c462-45a4-93d9-84a39b6d44ad 0 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 1 1d526d4a-c462-45a4-93d9-84a39b6d44ad
Such that: Such that:

View File

@@ -82,7 +82,9 @@ include::{samples-dir}websocket/src/main/java/sample/config/WebSecurityConfig.ja
---- ----
==== ====
$ ./gradlew :samples:websocket:bootRun ----
$ ./gradlew :samples:websocket:bootRun
----
You should now be able to access the application at http://localhost:8080/ You should now be able to access the application at http://localhost:8080/

View File

@@ -106,19 +106,19 @@ It looks something like the following:
---- ----
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
public SessionRepositoryRequestWrapper(HttpServletRequest original) { public SessionRepositoryRequestWrapper(HttpServletRequest original) {
super(original); super(original);
} }
public HttpSession getSession() { public HttpSession getSession() {
return getSession(true); return getSession(true);
} }
public HttpSession getSession(boolean createNew) { public HttpSession getSession(boolean createNew) {
// create an HttpSession implementation from Spring Session // create an HttpSession implementation from Spring Session
} }
// ... other methods delegate to the original HttpServletRequest ... // ... other methods delegate to the original HttpServletRequest ...
} }
---- ----
@@ -132,15 +132,15 @@ The pseudocode can be found below:
---- ----
public class SessionRepositoryFilter implements Filter { public class SessionRepositoryFilter implements Filter {
public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletRequest httpRequest = (HttpServletRequest) request;
SessionRepositoryRequestWrapper customRequest = SessionRepositoryRequestWrapper customRequest =
new SessionRepositoryRequestWrapper(httpRequest); new SessionRepositoryRequestWrapper(httpRequest);
chain.doFilter(customRequest, response, chain); chain.doFilter(customRequest, response, chain);
} }
// ... // ...
} }
---- ----
@@ -286,9 +286,9 @@ Each session is stored in Redis as a Hash.
Each session is set and updated using the HMSET command. Each session is set and updated using the HMSET command.
An example of how each session is stored can be seen below. An example of how each session is stored can be seen below.
HMSET spring:session:sessions:<session-id> creationTime 1404360000000 \ HMSET spring:session:sessions:<session-id> creationTime 1404360000000 \
maxInactiveInterval 1800 lastAccessedTime 1404360000000 \ maxInactiveInterval 1800 lastAccessedTime 1404360000000 \
sessionAttr:<attrName> someAttrValue sessionAttr:<attrName2> someAttrValue2 sessionAttr:<attrName> someAttrValue sessionAttr:<attrName2> someAttrValue2
[[api-redisoperationssessionrepository-expiration]] [[api-redisoperationssessionrepository-expiration]]
===== Session Expiration ===== Session Expiration
@@ -296,7 +296,7 @@ An example of how each session is stored can be seen below.
An expiration is associated to each session using the EXPIRE command based upon the RedisOperationsSessionRepository.RedisSession.getMaxInactiveInterval(). An expiration is associated to each session using the EXPIRE command based upon the RedisOperationsSessionRepository.RedisSession.getMaxInactiveInterval().
For example: For example:
EXPIRE spring:session:sessions:<session-id> 1800 EXPIRE spring:session:sessions:<session-id> 1800
Spring Session relies on the expired and delete http://redis.io/topics/notifications[keyspace notifications] from Redis to fire a <<SessionDestroyedEvent>>. Spring Session relies on the expired and delete http://redis.io/topics/notifications[keyspace notifications] from Redis to fire a <<SessionDestroyedEvent>>.
It is the `SessionDestroyedEvent` that ensures resources associated with the Session are cleaned up. It is the `SessionDestroyedEvent` that ensures resources associated with the Session are cleaned up.
@@ -313,8 +313,8 @@ 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. 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: For example:
SADD spring:session:expirations:<expire-rounded-up-to-nearest-minute> <session-id> SADD spring:session:expirations:<expire-rounded-up-to-nearest-minute> <session-id>
EXPIRE spring:session:expirations:<expire-rounded-up-to-nearest-minute> 1800 EXPIRE spring:session:expirations:<expire-rounded-up-to-nearest-minute> 1800
The background task will then use these mappings to explicitly request each key. 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. By accessing they key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
@@ -331,8 +331,8 @@ This means if an attribute is written once and read many times we only need to w
For example, assume the session attribute "sessionAttr2" from earlier was updated. For example, assume the session attribute "sessionAttr2" from earlier was updated.
The following would be executed upon saving: The following would be executed upon saving:
HMSET spring:session:sessions:<session-id> sessionAttr:<attrName2> newValue HMSET spring:session:sessions:<session-id> sessionAttr:<attrName2> newValue
EXPIRE spring:session:sessions:<session-id> 1800 EXPIRE spring:session:sessions:<session-id> 1800
[[api-redisoperationssessionrepository-sessiondestroyedevent]] [[api-redisoperationssessionrepository-sessiondestroyedevent]]
==== SessionDestroyedEvent ==== SessionDestroyedEvent
@@ -405,7 +405,7 @@ include::{indexdoc-tests}[tags=new-mapsessionrepository]
The <<samples,Hazelcast Sample>> is a complete application demonstrating using Spring Session with Hazelcast. The <<samples,Hazelcast Sample>> is a complete application demonstrating using Spring Session with Hazelcast.
To run it use the following: To run it use the following:
./gradlew :samples:hazelcast:tomcatRun ./gradlew :samples:hazelcast:tomcatRun
[[community]] [[community]]
== Spring Session Community == Spring Session Community

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -26,85 +26,85 @@ import static org.fest.assertions.Assertions.assertThat;
* @author Rob Winch * @author Rob Winch
*/ */
public class IndexDocTests { public class IndexDocTests {
static final String ATTR_USER = "user"; static final String ATTR_USER = "user";
@Test @Test
public void repositoryDemo() { public void repositoryDemo() {
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>(); ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
demo.repository = new MapSessionRepository(); demo.repository = new MapSessionRepository();
demo.demo(); demo.demo();
} }
// tag::repository-demo[] // tag::repository-demo[]
public class RepositoryDemo<S extends Session> { public class RepositoryDemo<S extends Session> {
private SessionRepository<S> repository; // <1> private SessionRepository<S> repository; // <1>
public void demo() { public void demo() {
S toSave = repository.createSession(); // <2> S toSave = repository.createSession(); // <2>
// <3> // <3>
User rwinch = new User("rwinch"); User rwinch = new User("rwinch");
toSave.setAttribute(ATTR_USER, rwinch); toSave.setAttribute(ATTR_USER, rwinch);
repository.save(toSave); // <4> repository.save(toSave); // <4>
S session = repository.getSession(toSave.getId()); // <5> S session = repository.getSession(toSave.getId()); // <5>
// <6> // <6>
User user = session.getAttribute(ATTR_USER); User user = session.getAttribute(ATTR_USER);
assertThat(user).isEqualTo(rwinch); assertThat(user).isEqualTo(rwinch);
} }
// ... setter methods ... // ... setter methods ...
} }
// end::repository-demo[] // end::repository-demo[]
@Test @Test
public void expireRepositoryDemo() { public void expireRepositoryDemo() {
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>(); ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
demo.repository = new MapSessionRepository(); demo.repository = new MapSessionRepository();
demo.demo(); demo.demo();
} }
// tag::expire-repository-demo[] // tag::expire-repository-demo[]
public class ExpiringRepositoryDemo<S extends ExpiringSession> { public class ExpiringRepositoryDemo<S extends ExpiringSession> {
private SessionRepository<S> repository; // <1> private SessionRepository<S> repository; // <1>
public void demo() { public void demo() {
S toSave = repository.createSession(); // <2> S toSave = repository.createSession(); // <2>
// ... // ...
toSave.setMaxInactiveIntervalInSeconds(30); // <3> toSave.setMaxInactiveIntervalInSeconds(30); // <3>
repository.save(toSave); // <4> repository.save(toSave); // <4>
S session = repository.getSession(toSave.getId()); // <5> S session = repository.getSession(toSave.getId()); // <5>
// ... // ...
} }
// ... setter methods ... // ... setter methods ...
} }
// end::expire-repository-demo[] // end::expire-repository-demo[]
@Test @Test
public void newRedisOperationsSessionRepository() { public void newRedisOperationsSessionRepository() {
// tag::new-redisoperationssessionrepository[] // tag::new-redisoperationssessionrepository[]
JedisConnectionFactory factory = new JedisConnectionFactory(); JedisConnectionFactory factory = new JedisConnectionFactory();
SessionRepository<? extends ExpiringSession> repository = SessionRepository<? extends ExpiringSession> repository =
new RedisOperationsSessionRepository(factory); new RedisOperationsSessionRepository(factory);
// end::new-redisoperationssessionrepository[] // end::new-redisoperationssessionrepository[]
} }
@Test @Test
public void mapRepository() { public void mapRepository() {
// tag::new-mapsessionrepository[] // tag::new-mapsessionrepository[]
SessionRepository<? extends ExpiringSession> repository = new MapSessionRepository(); SessionRepository<? extends ExpiringSession> repository = new MapSessionRepository();
// end::new-mapsessionrepository[] // end::new-mapsessionrepository[]
} }
private static class User { private static class User {
private User(String username) {} private User(String username) {}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -30,17 +30,17 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@EnableScheduling @EnableScheduling
@EnableWebSocketMessageBroker @EnableWebSocketMessageBroker
public class WebSocketConfig public class WebSocketConfig
extends AbstractWebSocketMessageBrokerConfigurer { extends AbstractWebSocketMessageBrokerConfigurer {
public void registerStompEndpoints(StompEndpointRegistry registry) { public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages") registry.addEndpoint("/messages")
.withSockJS(); .withSockJS();
} }
@Override @Override
public void configureMessageBroker(MessageBrokerRegistry registry) { public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/"); registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app"); registry.setApplicationDestinationPrefixes("/app");
} }
} }
// end::class[] // end::class[]

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package docs.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
/**
* @author Rob Winch
*/
public class WebSocketDocTests {
}

View File

@@ -14,73 +14,73 @@ targetCompatibility = 1.5
ext.springIoVersion = project.hasProperty('platformVersion') ? platformVersion : '1.1.0.BUILD-SNAPSHOT' ext.springIoVersion = project.hasProperty('platformVersion') ? platformVersion : '1.1.0.BUILD-SNAPSHOT'
ext.spockDependencies = [ ext.spockDependencies = [
dependencies.create("org.spockframework:spock-core:$spockVersion") { dependencies.create("org.spockframework:spock-core:$spockVersion") {
exclude group: 'junit', module: 'junit-dep' exclude group: 'junit', module: 'junit-dep'
} }
] ]
ext.gebDependencies = spockDependencies + [ ext.gebDependencies = spockDependencies + [
"org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion", "org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion",
"org.gebish:geb-spock:$gebVersion", "org.gebish:geb-spock:$gebVersion",
'commons-httpclient:commons-httpclient:3.1', 'commons-httpclient:commons-httpclient:3.1',
"org.codehaus.groovy:groovy:$groovyVersion" "org.codehaus.groovy:groovy:$groovyVersion"
] ]
ext.jstlDependencies = [ ext.jstlDependencies = [
"javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:$jstlVersion", "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:$jstlVersion",
"org.apache.taglibs:taglibs-standard-jstlel:1.2.1" "org.apache.taglibs:taglibs-standard-jstlel:1.2.1"
] ]
repositories { repositories {
mavenCentral() mavenCentral()
maven { url 'http://clojars.org/repo' } maven { url 'http://clojars.org/repo' }
maven { url 'https://repo.spring.io/libs-snapshot' } maven { url 'https://repo.spring.io/libs-snapshot' }
} }
configurations.all { configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details -> resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.springframework') { if (details.requested.group == 'org.springframework') {
details.useVersion springVersion details.useVersion springVersion
} }
} }
} }
// Integration test setup // Integration test setup
configurations { configurations {
integrationTestCompile { integrationTestCompile {
extendsFrom testCompile, optional, provided extendsFrom testCompile, optional, provided
} }
integrationTestRuntime { integrationTestRuntime {
extendsFrom integrationTestCompile, testRuntime extendsFrom integrationTestCompile, testRuntime
} }
} }
sourceSets { sourceSets {
integrationTest { integrationTest {
java.srcDir file('src/integration-test/java') java.srcDir file('src/integration-test/java')
groovy.srcDirs file('src/integration-test/groovy') groovy.srcDirs file('src/integration-test/groovy')
resources.srcDir file('src/integration-test/resources') resources.srcDir file('src/integration-test/resources')
compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integrationTestCompile compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integrationTestCompile
runtimeClasspath = output + compileClasspath + configurations.integrationTestRuntime runtimeClasspath = output + compileClasspath + configurations.integrationTestRuntime
} }
} }
task integrationTest(type: Test, dependsOn: jar) { task integrationTest(type: Test, dependsOn: jar) {
testClassesDir = sourceSets.integrationTest.output.classesDir testClassesDir = sourceSets.integrationTest.output.classesDir
logging.captureStandardOutput(LogLevel.INFO) logging.captureStandardOutput(LogLevel.INFO)
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
maxParallelForks = 1 maxParallelForks = 1
reports { reports {
html.destination = project.file("$project.buildDir/reports/integration-tests/") html.destination = project.file("$project.buildDir/reports/integration-tests/")
junitXml.destination = project.file("$project.buildDir/integration-test-results/") junitXml.destination = project.file("$project.buildDir/integration-test-results/")
} }
} }
eclipse { eclipse {
classpath { classpath {
plusConfigurations += [ configurations.integrationTestCompile ] plusConfigurations += [ configurations.integrationTestCompile ]
} }
} }
project.idea.module { project.idea.module {
scopes.TEST.plus += [project.configurations.integrationTestRuntime] scopes.TEST.plus += [project.configurations.integrationTestRuntime]
} }

View File

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

View File

@@ -1,71 +1,71 @@
buildscript { buildscript {
repositories { repositories {
maven { url "https://repo.spring.io/plugins-release" } maven { url "https://repo.spring.io/plugins-release" }
} }
dependencies { dependencies {
classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3") classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3")
} }
} }
apply plugin: 'war' apply plugin: 'war'
apply plugin: 'tomcat' apply plugin: 'tomcat'
dependencies { dependencies {
def tomcatVersion = '7.0.54' def tomcatVersion = '7.0.54'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}", tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}" "org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}"
tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") { tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj' exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
} }
} }
[tomcatRun,tomcatRunWar]*.contextPath = '/' [tomcatRun,tomcatRunWar]*.contextPath = '/'
task integrationTomcatRun(type: org.gradle.api.plugins.tomcat.tasks.TomcatRun) { task integrationTomcatRun(type: org.gradle.api.plugins.tomcat.tasks.TomcatRun) {
onlyIf { !sourceSets.integrationTest.allSource.empty } onlyIf { !sourceSets.integrationTest.allSource.empty }
buildscriptClasspath = tomcatRun.buildscriptClasspath buildscriptClasspath = tomcatRun.buildscriptClasspath
contextPath = tomcatRun.contextPath contextPath = tomcatRun.contextPath
daemon = true daemon = true
tomcatClasspath = tomcatRun.tomcatClasspath tomcatClasspath = tomcatRun.tomcatClasspath
webAppClasspath = tomcatRun.webAppClasspath webAppClasspath = tomcatRun.webAppClasspath
webAppSourceDirectory = tomcatRun.webAppSourceDirectory webAppSourceDirectory = tomcatRun.webAppSourceDirectory
doFirst { doFirst {
def mainOutputDir = project.sourceSets.main.output.classesDir def mainOutputDir = project.sourceSets.main.output.classesDir
if(mainOutputDir) { if(mainOutputDir) {
classesDirectory = mainOutputDir classesDirectory = mainOutputDir
} }
// delay reserving ports to ensure they are still available // delay reserving ports to ensure they are still available
def ports = reservePorts(3) def ports = reservePorts(3)
httpPort = ports[0] httpPort = ports[0]
ajpPort = ports[1] ajpPort = ports[1]
stopPort = ports[2] stopPort = ports[2]
} }
} }
task integrationTomcatStop(type: org.gradle.api.plugins.tomcat.tasks.TomcatStop) { task integrationTomcatStop(type: org.gradle.api.plugins.tomcat.tasks.TomcatStop) {
onlyIf { !sourceSets.integrationTest.allSource.empty } onlyIf { !sourceSets.integrationTest.allSource.empty }
doFirst { doFirst {
stopPort = integrationTomcatRun.stopPort stopPort = integrationTomcatRun.stopPort
} }
} }
integrationTest { integrationTest {
dependsOn integrationTomcatRun dependsOn integrationTomcatRun
doFirst { doFirst {
def host = 'localhost:' + integrationTomcatRun.httpPort def host = 'localhost:' + integrationTomcatRun.httpPort
systemProperties['geb.build.baseUrl'] = 'http://'+host+'/' + integrationTomcatRun.contextPath systemProperties['geb.build.baseUrl'] = 'http://'+host+'/' + integrationTomcatRun.contextPath
systemProperties['geb.build.reportsDir'] = 'build/geb-reports' systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
} }
finalizedBy integrationTomcatStop finalizedBy integrationTomcatStop
} }
def reservePorts(int count) { def reservePorts(int count) {
def sockets = [] def sockets = []
for(int i in 1..count) { for(int i in 1..count) {
sockets << new ServerSocket(0) sockets << new ServerSocket(0)
} }
def result = sockets*.localPort def result = sockets*.localPort
sockets*.close() sockets*.close()
result result
} }

View File

@@ -1,10 +1,10 @@
buildscript { buildscript {
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
} }
} }
apply plugin: 'spring-boot' apply plugin: 'spring-boot'
@@ -16,36 +16,36 @@ tasks.findByPath("artifactoryPublish")?.enabled = false
group = 'samples' group = 'samples'
dependencies { dependencies {
compile project(':spring-session-data-redis'), compile project(':spring-session-data-redis'),
"org.springframework.boot:spring-boot-starter-web", "org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-thymeleaf", "org.springframework.boot:spring-boot-starter-thymeleaf",
"redis.embedded:embedded-redis:0.2", "redis.embedded:embedded-redis:0.2",
"nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect", "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect",
"org.springframework.security:spring-security-web:$springSecurityVersion", "org.springframework.security:spring-security-web:$springSecurityVersion",
"org.springframework.security:spring-security-config:$springSecurityVersion" "org.springframework.security:spring-security-config:$springSecurityVersion"
testCompile "org.springframework.boot:spring-boot-starter-test" testCompile "org.springframework.boot:spring-boot-starter-test"
integrationTestCompile gebDependencies, integrationTestCompile gebDependencies,
"org.spockframework:spock-spring:$spockVersion" "org.spockframework:spock-spring:$spockVersion"
} }
integrationTest { integrationTest {
doFirst { doFirst {
def port = reservePort() def port = reservePort()
def host = 'localhost:' + port def host = 'localhost:' + port
systemProperties['geb.build.baseUrl'] = 'http://'+host+'/' systemProperties['geb.build.baseUrl'] = 'http://'+host+'/'
systemProperties['geb.build.reportsDir'] = 'build/geb-reports' systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
systemProperties['server.port'] = port systemProperties['server.port'] = port
systemProperties['management.port'] = 0 systemProperties['management.port'] = 0
} }
} }
def reservePort() { def reservePort() {
def socket = new ServerSocket(0) def socket = new ServerSocket(0)
def result = socket.localPort def result = socket.localPort
socket.close() socket.close()
result result
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample
import geb.spock.* import geb.spock.*
@@ -23,37 +38,37 @@ import pages.*
@IntegrationTest @IntegrationTest
class BootTests extends GebReportingSpec { class BootTests extends GebReportingSpec {
def 'Unauthenticated user sent to log in page'() { def 'Unauthenticated user sent to log in page'() {
when: 'unauthenticated user request protected page' when: 'unauthenticated user request protected page'
via HomePage via HomePage
then: 'sent to the log in page' then: 'sent to the log in page'
at LoginPage at LoginPage
} }
def 'Log in views home page'() { def 'Log in views home page'() {
when: 'log in successfully' when: 'log in successfully'
login() login()
then: 'sent to original page' then: 'sent to original page'
at HomePage at HomePage
and: 'the username is displayed' and: 'the username is displayed'
username == 'user' username == 'user'
and: 'Spring Session Management is being used' and: 'Spring Session Management is being used'
driver.manage().cookies.find { it.name == 'SESSION' } driver.manage().cookies.find { it.name == 'SESSION' }
and: 'Standard Session is NOT being used' and: 'Standard Session is NOT being used'
!driver.manage().cookies.find { it.name == 'JSESSIONID' } !driver.manage().cookies.find { it.name == 'JSESSIONID' }
} }
def 'Log out success'() { def 'Log out success'() {
when: when:
logout() logout()
then: then:
at LoginPage at LoginPage
} }
def 'Logged out user sent to log in page'() { def 'Logged out user sent to log in page'() {
when: 'logged out user request protected page' when: 'logged out user request protected page'
via HomePage via HomePage
then: 'sent to the log in page' then: 'sent to the log in page'
at LoginPage at LoginPage
} }
} }

View File

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

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample.pages
import geb.* import geb.*
@@ -8,15 +23,15 @@ import geb.*
* @author Rob Winch * @author Rob Winch
*/ */
class LoginPage extends Page { class LoginPage extends Page {
static url = '/login' static url = '/login'
static at = { assert driver.title == 'Login Page'; true} static at = { assert driver.title == 'Login Page'; true}
static content = { static content = {
form { $('form') } form { $('form') }
submit { $('input[type=submit]') } submit { $('input[type=submit]') }
login(required:false) { user='user', pass='password' -> login(required:false) { user='user', pass='password' ->
form.username = user form.username = user
form.password = pass form.password = pass
submit.click(HomePage) submit.click(HomePage)
} }
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration;
@EnableAutoConfiguration @EnableAutoConfiguration
public class Application { public class Application {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
} }
} }

View File

@@ -1,19 +1,19 @@
package sample.config;
/* /*
* Copyright 2002-2014 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 * Licensed under the Apache License, Version 2.0 (the "License");
* use this file except in compliance with the License. You may obtain a copy of * you may not use this file except in compliance with the License.
* the License at * You may obtain a copy of the License at
* *
* http://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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS,
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* License for the specific language governing permissions and limitations under * See the License for the specific language governing permissions and
* the License. * limitations under the License.
*/ */
package sample.config;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
@@ -36,35 +36,33 @@ import redis.embedded.RedisServer;
@Configuration @Configuration
public class EmbeddedRedisConfiguration { public class EmbeddedRedisConfiguration {
@Bean @Bean
public static RedisServerBean redisServer() { public static RedisServerBean redisServer() {
return new RedisServerBean(); return new RedisServerBean();
} }
/** /**
* Implements BeanDefinitionRegistryPostProcessor to ensure this Bean * Implements BeanDefinitionRegistryPostProcessor to ensure this Bean
* is initialized before any other Beans. Specifically, we want to ensure * is initialized before any other Beans. Specifically, we want to ensure
* that the Redis Server is started before RedisHttpSessionConfiguration * that the Redis Server is started before RedisHttpSessionConfiguration
* attempts to enable Keyspace notifications. * attempts to enable Keyspace notifications.
*/ */
static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor {
private RedisServer redisServer; private RedisServer redisServer;
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start(); redisServer.start();
} }
public void destroy() throws Exception { public void destroy() throws Exception {
if(redisServer != null) { if(redisServer != null) {
redisServer.stop(); redisServer.stop();
} }
} }
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} }
}
} }

View File

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

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -26,10 +26,10 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
@EnableWebSecurity @EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth auth
.inMemoryAuthentication() .inMemoryAuthentication()
.withUser("user").password("password").roles("USER"); .withUser("user").password("password").roles("USER");
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -26,8 +26,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
*/ */
@Controller @Controller
public class IndexController { public class IndexController {
@RequestMapping("/") @RequestMapping("/")
public String index() { public String index() {
return "index"; return "index";
} }
} }

View File

@@ -1,11 +1,11 @@
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout"> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
<head> <head>
<title>Secured Content</title> <title>Secured Content</title>
</head> </head>
<body> <body>
<div layout:fragment="content"> <div layout:fragment="content">
<h1>Secured Page</h1> <h1>Secured Page</h1>
<p>This page is secured using Spring Boot, Spring Session, and Spring Security.</p> <p>This page is secured using Spring Boot, Spring Session, and Spring Security.</p>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,122 +1,122 @@
<!DOCTYPE html SYSTEM "http://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" <html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head> <head>
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Spring Session Sample</title> <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 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> <link th:href="@{/resources/css/bootstrap.css}" href="../static/css/bootstrap.css" rel="stylesheet"></link>
<style type="text/css"> <style type="text/css">
/* Sticky footer styles /* Sticky footer styles
-------------------------------------------------- */ -------------------------------------------------- */
html, html,
body { body {
height: 100%; height: 100%;
/* The html and body elements cannot have any padding or margin. */ /* The html and body elements cannot have any padding or margin. */
} }
/* Wrapper for page content to push down footer */ /* Wrapper for page content to push down footer */
#wrap { #wrap {
min-height: 100%; min-height: 100%;
height: auto !important; height: auto !important;
height: 100%; height: 100%;
/* Negative indent footer by it's height */ /* Negative indent footer by it's height */
margin: 0 auto -60px; margin: 0 auto -60px;
} }
/* Set the fixed height of the footer here */ /* Set the fixed height of the footer here */
#push, #push,
#footer { #footer {
height: 60px; height: 60px;
} }
#footer { #footer {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
/* Lastly, apply responsive CSS fixes as necessary */ /* Lastly, apply responsive CSS fixes as necessary */
@media (max-width: 767px) { @media (max-width: 767px) {
#footer { #footer {
margin-left: -20px; margin-left: -20px;
margin-right: -20px; margin-right: -20px;
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
} }
} }
/* Custom page CSS /* Custom page CSS
-------------------------------------------------- */ -------------------------------------------------- */
/* Not required for template or sticky footer method. */ /* Not required for template or sticky footer method. */
.container { .container {
width: auto; width: auto;
max-width: 680px; max-width: 680px;
} }
.container .credit { .container .credit {
margin: 20px 0; margin: 20px 0;
text-align: center; text-align: center;
} }
a { a {
color: green; color: green;
} }
.navbar-form { .navbar-form {
margin-left: 1em; margin-left: 1em;
} }
</style> </style>
<link th:href="@{resources/css/bootstrap-responsive.css}" href="/static/css/bootstrap-responsive.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 --> <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--> <![endif]-->
</head> </head>
<body> <body>
<div id="wrap"> <div id="wrap">
<div class="navbar navbar-inverse navbar-static-top"> <div class="navbar navbar-inverse navbar-static-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" th:href="@{/}"><img th:src="@{/resources/img/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" <div class="nav-collapse collapse"
th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}"> th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}">
<div th:if="${currentUser != null}"> <div th:if="${currentUser != null}">
<form class="navbar-form pull-right" th:action="@{/logout}" method="post"> <form class="navbar-form pull-right" th:action="@{/logout}" method="post">
<input type="submit" value="Log out" /> <input type="submit" value="Log out" />
</form> </form>
<p id="un" class="navbar-text pull-right" th:text="${currentUser.username}"> <p id="un" class="navbar-text pull-right" th:text="${currentUser.username}">
sample_user sample_user
</p> </p>
</div> </div>
<ul class="nav"> <ul class="nav">
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Begin page content --> <!-- Begin page content -->
<div class="container"> <div class="container">
<div class="alert alert-success" <div class="alert alert-success"
th:if="${globalMessage}" th:if="${globalMessage}"
th:text="${globalMessage}"> th:text="${globalMessage}">
Some Success message Some Success message
</div> </div>
<div layout:fragment="content"> <div layout:fragment="content">
Fake content Fake content
</div> </div>
</div> </div>
<div id="push"><!-- --></div> <div id="push"><!-- --></div>
</div> </div>
<div id="footer"> <div id="footer">
<div class="container"> <div class="container">
<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> <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>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -3,17 +3,17 @@ apply from: TOMCAT_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner { sonarRunner {
skipProject = true skipProject = true
} }
dependencies { dependencies {
compile project(':spring-session'), compile project(':spring-session'),
"com.hazelcast:hazelcast-client:3.3.3", "com.hazelcast:hazelcast-client:3.3.3",
jstlDependencies jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
integrationTestCompile gebDependencies integrationTestCompile gebDependencies
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample
import geb.spock.* import geb.spock.*
@@ -12,19 +27,19 @@ import pages.*
*/ */
@Stepwise @Stepwise
class AttributeTests extends GebReportingSpec { class AttributeTests extends GebReportingSpec {
def 'first visit no attributes'() { def 'first visit no attributes'() {
when: when:
to HomePage to HomePage
then: then:
attributes.empty attributes.empty
} }
def 'create attribute'() { def 'create attribute'() {
when: when:
createAttribute('a','b') createAttribute('a','b')
then: then:
attributes.size() == 1 attributes.size() == 1
attributes[0].name == 'a' attributes[0].name == 'a'
attributes[0].value == 'b' attributes[0].value == 'b'
} }
} }

View File

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

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -40,51 +40,51 @@ import java.util.Map;
@WebListener @WebListener
public class Initializer implements ServletContextListener { public class Initializer implements ServletContextListener {
private HazelcastInstance instance; private HazelcastInstance instance;
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
String sessionMapName = "spring:session:sessions"; String sessionMapName = "spring:session:sessions";
ServletContext sc = sce.getServletContext(); ServletContext sc = sce.getServletContext();
Config cfg = new Config(); Config cfg = new Config();
NetworkConfig netConfig = new NetworkConfig(); NetworkConfig netConfig = new NetworkConfig();
netConfig.setPort(getAvailablePort()); netConfig.setPort(getAvailablePort());
cfg.setNetworkConfig(netConfig); cfg.setNetworkConfig(netConfig);
SerializerConfig serializer = new SerializerConfig() SerializerConfig serializer = new SerializerConfig()
.setTypeClass(Object.class) .setTypeClass(Object.class)
.setImplementation(new ObjectStreamSerializer()); .setImplementation(new ObjectStreamSerializer());
cfg.getSerializationConfig().addSerializerConfig(serializer); cfg.getSerializationConfig().addSerializerConfig(serializer);
MapConfig mc = new MapConfig(); MapConfig mc = new MapConfig();
mc.setName(sessionMapName); mc.setName(sessionMapName);
mc.setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); mc.setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
cfg.addMapConfig(mc); cfg.addMapConfig(mc);
instance = Hazelcast.newHazelcastInstance(cfg); instance = Hazelcast.newHazelcastInstance(cfg);
Map<String,ExpiringSession> sessions = instance.getMap(sessionMapName); Map<String,ExpiringSession> sessions = instance.getMap(sessionMapName);
SessionRepository<ExpiringSession> sessionRepository = SessionRepository<ExpiringSession> sessionRepository =
new MapSessionRepository(sessions); new MapSessionRepository(sessions);
SessionRepositoryFilter<ExpiringSession> filter = SessionRepositoryFilter<ExpiringSession> filter =
new SessionRepositoryFilter<ExpiringSession>(sessionRepository); new SessionRepositoryFilter<ExpiringSession>(sessionRepository);
Dynamic fr = sc.addFilter("springSessionFilter", filter); Dynamic fr = sc.addFilter("springSessionFilter", filter);
fr.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); fr.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
} }
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
instance.shutdown(); instance.shutdown();
} }
private static int getAvailablePort() { private static int getAvailablePort() {
ServerSocket socket = null; ServerSocket socket = null;
try { try {
socket = new ServerSocket(0); socket = new ServerSocket(0);
return socket.getLocalPort(); return socket.getLocalPort();
} catch(IOException e) { } catch(IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {
try { try {
socket.close(); socket.close();
}catch(IOException e) {} }catch(IOException e) {}
} }
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -32,28 +32,28 @@ import com.hazelcast.nio.serialization.StreamSerializer;
* *
*/ */
public class ObjectStreamSerializer implements StreamSerializer<Object> { public class ObjectStreamSerializer implements StreamSerializer<Object> {
public int getTypeId() { public int getTypeId() {
return 2; return 2;
} }
public void write(ObjectDataOutput objectDataOutput, Object object) public void write(ObjectDataOutput objectDataOutput, Object object)
throws IOException { throws IOException {
ObjectOutputStream out = new ObjectOutputStream((OutputStream) objectDataOutput); ObjectOutputStream out = new ObjectOutputStream((OutputStream) objectDataOutput);
out.writeObject(object); out.writeObject(object);
out.flush(); out.flush();
} }
public Object read(ObjectDataInput objectDataInput) public Object read(ObjectDataInput objectDataInput)
throws IOException { throws IOException {
ObjectInputStream in = new ObjectInputStream((InputStream) objectDataInput); ObjectInputStream in = new ObjectInputStream((InputStream) objectDataInput);
try { try {
return in.readObject(); return in.readObject();
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new IOException(e); throw new IOException(e);
} }
} }
public void destroy() { public void destroy() {
} }
} }

View File

@@ -1,19 +1,19 @@
package sample;
/* /*
* Copyright 2002-2014 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 * Licensed under the Apache License, Version 2.0 (the "License");
* use this file except in compliance with the License. You may obtain a copy of * you may not use this file except in compliance with the License.
* the License at * You may obtain a copy of the License at
* *
* http://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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS,
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* License for the specific language governing permissions and limitations under * See the License for the specific language governing permissions and
* the License. * limitations under the License.
*/ */
package sample;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet; import javax.servlet.annotation.WebServlet;
@@ -28,13 +28,13 @@ import java.io.IOException;
@WebServlet("/session") @WebServlet("/session")
public class SessionServlet extends HttpServlet { public class SessionServlet extends HttpServlet {
@Override @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 attributeName = req.getParameter("attributeName");
String attributeValue = req.getParameter("attributeValue"); String attributeValue = req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue); req.getSession().setAttribute(attributeName, attributeValue);
resp.sendRedirect(req.getContextPath() + "/"); resp.sendRedirect(req.getContextPath() + "/");
} }
private static final long serialVersionUID = 2878267318695777395L; private static final long serialVersionUID = 2878267318695777395L;
} }

View File

@@ -3,18 +3,18 @@ apply from: TOMCAT_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner { sonarRunner {
skipProject = true skipProject = true
} }
dependencies { dependencies {
compile project(':spring-session-data-redis'), compile project(':spring-session-data-redis'),
"org.springframework:spring-web:$springVersion", "org.springframework:spring-web:$springVersion",
"redis.embedded:embedded-redis:0.2", "redis.embedded:embedded-redis:0.2",
jstlDependencies jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
integrationTestCompile gebDependencies integrationTestCompile gebDependencies
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample
import geb.spock.* import geb.spock.*
@@ -12,19 +27,19 @@ import pages.*
*/ */
@Stepwise @Stepwise
class AttributeTests extends GebReportingSpec { class AttributeTests extends GebReportingSpec {
def 'first visit no attributes'() { def 'first visit no attributes'() {
when: when:
to HomePage to HomePage
then: then:
attributes.empty attributes.empty
} }
def 'create attribute'() { def 'create attribute'() {
when: when:
createAttribute('a','b') createAttribute('a','b')
then: then:
attributes.size() == 1 attributes.size() == 1
attributes[0].name == 'a' attributes[0].name == 'a'
attributes[0].value == 'b' attributes[0].value == 'b'
} }
} }

View File

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

View File

@@ -1,3 +1,18 @@
/*
* 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; package sample;
import org.springframework.session.data.redis.config.annotation.web.http.*; import org.springframework.session.data.redis.config.annotation.web.http.*;

View File

@@ -1,6 +1,5 @@
package sample;
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -14,14 +13,14 @@ package sample;
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package sample;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol;
import redis.embedded.RedisServer; import redis.embedded.RedisServer;
@@ -34,21 +33,21 @@ import redis.embedded.RedisServer;
* @author Rob Winch * @author Rob Winch
*/ */
public class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { public class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor {
private RedisServer redisServer; private RedisServer redisServer;
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start(); redisServer.start();
} }
public void destroy() throws Exception { public void destroy() throws Exception {
if(redisServer != null) { if(redisServer != null) {
redisServer.stop(); redisServer.stop();
} }
} }
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {} public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
} }

View File

@@ -1,3 +1,18 @@
/*
* 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; package sample;
import javax.servlet.*; import javax.servlet.*;
@@ -5,16 +20,19 @@ import javax.servlet.annotation.*;
import javax.servlet.http.*; import javax.servlet.http.*;
import java.io.IOException; import java.io.IOException;
// tag::class[]
@WebServlet("/session") @WebServlet("/session")
public class SessionServlet extends HttpServlet { public class SessionServlet extends HttpServlet {
@Override @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 attributeName = req.getParameter("attributeName");
String attributeValue = req.getParameter("attributeValue"); String attributeValue = req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue); req.getSession().setAttribute(attributeName, attributeValue);
resp.sendRedirect(req.getContextPath() + "/"); resp.sendRedirect(req.getContextPath() + "/");
} }
private static final long serialVersionUID = 2878267318695777395L; private static final long serialVersionUID = 2878267318695777395L;
} }
// end::class[]

View File

@@ -3,18 +3,18 @@ apply from: TOMCAT_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner { sonarRunner {
skipProject = true skipProject = true
} }
dependencies { dependencies {
compile project(':spring-session-data-redis'), compile project(':spring-session-data-redis'),
"org.springframework:spring-web:$springVersion", "org.springframework:spring-web:$springVersion",
"redis.embedded:embedded-redis:0.2", "redis.embedded:embedded-redis:0.2",
jstlDependencies jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
integrationTestCompile gebDependencies integrationTestCompile gebDependencies
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample
import geb.spock.* import geb.spock.*
@@ -12,19 +27,19 @@ import pages.*
*/ */
@Stepwise @Stepwise
class AttributeTests extends GebReportingSpec { class AttributeTests extends GebReportingSpec {
def 'first visit no attributes'() { def 'first visit no attributes'() {
when: when:
to HomePage to HomePage
then: then:
attributes.empty attributes.empty
} }
def 'create attribute'() { def 'create attribute'() {
when: when:
createAttribute('a','b') createAttribute('a','b')
then: then:
attributes.size() == 1 attributes.size() == 1
attributes[0].name == 'a' attributes[0].name == 'a'
attributes[0].value == 'b' attributes[0].value == 'b'
} }
} }

View File

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

View File

@@ -1,17 +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; package sample;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.jedis.*; import org.springframework.data.redis.connection.jedis.*;
import org.springframework.session.data.redis.config.annotation.web.http.*; import org.springframework.session.data.redis.config.annotation.web.http.*;
// tag::class[]
@Import(EmbeddedRedisConfiguration.class) // <1> @Import(EmbeddedRedisConfiguration.class) // <1>
@EnableRedisHttpSession // <2> @EnableRedisHttpSession // <2>
public class Config { public class Config {
@Bean @Bean
public JedisConnectionFactory connectionFactory() { public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(); // <3> return new JedisConnectionFactory(); // <3>
} }
} }
// end::class[]

View File

@@ -1,6 +1,5 @@
package sample;
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -14,6 +13,8 @@ package sample;
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package sample;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@@ -36,34 +37,34 @@ import redis.embedded.RedisServer;
@Configuration @Configuration
public class EmbeddedRedisConfiguration { public class EmbeddedRedisConfiguration {
@Bean @Bean
public static RedisServerBean redisServer() { public static RedisServerBean redisServer() {
return new RedisServerBean(); return new RedisServerBean();
} }
/** /**
* Implements BeanDefinitionRegistryPostProcessor to ensure this Bean * Implements BeanDefinitionRegistryPostProcessor to ensure this Bean
* is initialized before any other Beans. Specifically, we want to ensure * is initialized before any other Beans. Specifically, we want to ensure
* that the Redis Server is started before RedisHttpSessionConfiguration * that the Redis Server is started before RedisHttpSessionConfiguration
* attempts to enable Keyspace notifications. * attempts to enable Keyspace notifications.
*/ */
static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor {
private RedisServer redisServer; private RedisServer redisServer;
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start(); redisServer.start();
} }
public void destroy() throws Exception { public void destroy() throws Exception {
if(redisServer != null) { if(redisServer != null) {
redisServer.stop(); redisServer.stop();
} }
} }
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {} public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
} }
} }

View File

@@ -1,11 +1,28 @@
/*
* 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; package sample;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
// tag::class[]
public class Initializer public class Initializer
extends AbstractHttpSessionApplicationInitializer { // <1> extends AbstractHttpSessionApplicationInitializer { // <1>
public Initializer() { public Initializer() {
super(Config.class); // <2> super(Config.class); // <2>
} }
} }
// end::class[]

View File

@@ -1,3 +1,18 @@
/*
* 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; package sample;
import javax.servlet.*; import javax.servlet.*;
@@ -5,16 +20,18 @@ import javax.servlet.annotation.*;
import javax.servlet.http.*; import javax.servlet.http.*;
import java.io.IOException; import java.io.IOException;
// tag::class[]
@WebServlet("/session") @WebServlet("/session")
public class SessionServlet extends HttpServlet { public class SessionServlet extends HttpServlet {
@Override @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 attributeName = req.getParameter("attributeName");
String attributeValue = req.getParameter("attributeValue"); String attributeValue = req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue); req.getSession().setAttribute(attributeName, attributeValue);
resp.sendRedirect(req.getContextPath() + "/"); resp.sendRedirect(req.getContextPath() + "/");
} }
private static final long serialVersionUID = 2878267318695777395L; private static final long serialVersionUID = 2878267318695777395L;
} }
// tag::end[]

View File

@@ -2,47 +2,47 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Session Attributes</title> <title>Session Attributes</title>
<link rel="stylesheet" href="assets/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap.min.css">
<style type="text/css"> <style type="text/css">
body { body {
padding: 1em; padding: 1em;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>Description</h1> <h1>Description</h1>
<p>This application demonstrates how to use a Redis instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p> <p>This application demonstrates how to use a Redis instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p>
<h1>Try it</h1> <h1>Try it</h1>
<form class="form-inline" role="form" action="./session" method="post"> <form class="form-inline" role="form" action="./session" method="post">
<label for="attributeValue">Attribute Name</label> <label for="attributeValue">Attribute Name</label>
<input id="attributeValue" type="text" name="attributeName"/> <input id="attributeValue" type="text" name="attributeName"/>
<label for="attributeValue">Attribute Value</label> <label for="attributeValue">Attribute Value</label>
<input id="attributeValue" type="text" name="attributeValue"/> <input id="attributeValue" type="text" name="attributeValue"/>
<input type="submit" value="Set Attribute"/> <input type="submit" value="Set Attribute"/>
</form> </form>
<hr/> <hr/>
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Attribute Name</th> <th>Attribute Name</th>
<th>Attribute Value</th> <th>Attribute Value</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<c:forEach items="${sessionScope}" var="attr"> <c:forEach items="${sessionScope}" var="attr">
<tr> <tr>
<td><c:out value="${attr.key}"/></td> <td><c:out value="${attr.key}"/></td>
<td><c:out value="${attr.value}"/></td> <td><c:out value="${attr.value}"/></td>
</tr> </tr>
</c:forEach> </c:forEach>
</tbody> </tbody>
</table> </table>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -3,22 +3,22 @@ apply from: TOMCAT_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner { sonarRunner {
skipProject = true skipProject = true
} }
dependencies { dependencies {
compile project(':spring-session-data-redis'), compile project(':spring-session-data-redis'),
"org.springframework:spring-webmvc:$springVersion", "org.springframework:spring-webmvc:$springVersion",
"redis.embedded:embedded-redis:0.2", "redis.embedded:embedded-redis:0.2",
"org.springframework.security:spring-security-config:$springSecurityVersion", "org.springframework.security:spring-security-config:$springSecurityVersion",
"org.springframework.security:spring-security-web:$springSecurityVersion", "org.springframework.security:spring-security-web:$springSecurityVersion",
"com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion",
jstlDependencies jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
integrationTestCompile spockDependencies, integrationTestCompile spockDependencies,
'org.codehaus.groovy.modules.http-builder:http-builder:0.7' 'org.codehaus.groovy.modules.http-builder:http-builder:0.7'
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample
import groovyx.net.http.HttpResponseException import groovyx.net.http.HttpResponseException

View File

@@ -1,19 +1,19 @@
package sample;
/* /*
* Copyright 2002-2014 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 * Licensed under the Apache License, Version 2.0 (the "License");
* use this file except in compliance with the License. You may obtain a copy of * you may not use this file except in compliance with the License.
* the License at * You may obtain a copy of the License at
* *
* http://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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS,
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* License for the specific language governing permissions and limitations under * See the License for the specific language governing permissions and
* the License. * limitations under the License.
*/ */
package sample;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@@ -36,35 +36,33 @@ import redis.embedded.RedisServer;
@Configuration @Configuration
public class EmbeddedRedisConfiguration { public class EmbeddedRedisConfiguration {
@Bean @Bean
public static RedisServerBean redisServer() { public static RedisServerBean redisServer() {
return new RedisServerBean(); return new RedisServerBean();
} }
/** /**
* Implements BeanDefinitionRegistryPostProcessor to ensure this Bean * Implements BeanDefinitionRegistryPostProcessor to ensure this Bean
* is initialized before any other Beans. Specifically, we want to ensure * is initialized before any other Beans. Specifically, we want to ensure
* that the Redis Server is started before RedisHttpSessionConfiguration * that the Redis Server is started before RedisHttpSessionConfiguration
* attempts to enable Keyspace notifications. * attempts to enable Keyspace notifications.
*/ */
static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor {
private RedisServer redisServer; private RedisServer redisServer;
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start(); redisServer.start();
} }
public void destroy() throws Exception { public void destroy() throws Exception {
if(redisServer != null) { if(redisServer != null) {
redisServer.stop(); redisServer.stop();
} }
} }
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} }
}
} }

View File

@@ -1,3 +1,18 @@
/*
* 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; package sample;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -8,18 +23,20 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableR
import org.springframework.session.web.http.HeaderHttpSessionStrategy; import org.springframework.session.web.http.HeaderHttpSessionStrategy;
import org.springframework.session.web.http.HttpSessionStrategy; import org.springframework.session.web.http.HttpSessionStrategy;
// tag::class[]
@Configuration @Configuration
@Import(EmbeddedRedisConfiguration.class) // <1> @Import(EmbeddedRedisConfiguration.class) // <1>
@EnableRedisHttpSession // <2> @EnableRedisHttpSession // <2>
public class HttpSessionConfig { public class HttpSessionConfig {
@Bean @Bean
public JedisConnectionFactory connectionFactory() { public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(); // <3> return new JedisConnectionFactory(); // <3>
} }
@Bean @Bean
public HttpSessionStrategy httpSessionStrategy() { public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy(); // <4> return new HeaderHttpSessionStrategy(); // <4>
} }
} }
// end::class[]

View File

@@ -1,7 +1,24 @@
/*
* 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; package sample;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
// tag::class[]
public class Initializer extends AbstractHttpSessionApplicationInitializer { public class Initializer extends AbstractHttpSessionApplicationInitializer {
} }
// end::class[]

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -28,19 +28,19 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
@EnableWebSecurity @EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http http
.authorizeRequests() .authorizeRequests()
.anyRequest().authenticated() .anyRequest().authenticated()
.and() .and()
.httpBasic(); .httpBasic();
} }
@Autowired @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth auth
.inMemoryAuthentication() .inMemoryAuthentication()
.withUser("user").password("password").roles("USER"); .withUser("user").password("password").roles("USER");
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -21,6 +21,6 @@ import org.springframework.security.web.context.AbstractSecurityWebApplicationIn
* @author Rob Winch * @author Rob Winch
*/ */
public class SecurityInitializer extends public class SecurityInitializer extends
AbstractSecurityWebApplicationInitializer { AbstractSecurityWebApplicationInitializer {
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -23,20 +23,20 @@ import sample.SecurityConfig;
* @author Rob Winch * @author Rob Winch
*/ */
public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// tag::config[] // tag::config[]
@Override @Override
protected Class<?>[] getRootConfigClasses() { protected Class<?>[] getRootConfigClasses() {
return new Class[] {SecurityConfig.class, HttpSessionConfig.class}; return new Class[] {SecurityConfig.class, HttpSessionConfig.class};
} }
// end::config[] // end::config[]
@Override @Override
protected Class<?>[] getServletConfigClasses() { protected Class<?>[] getServletConfigClasses() {
return new Class[] { MvcConfig.class }; return new Class[] { MvcConfig.class };
} }
@Override @Override
protected String[] getServletMappings() { protected String[] getServletMappings() {
return new String[] { "/" }; return new String[] { "/" };
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -15,33 +15,33 @@
*/ */
package sample.mvc; package sample.mvc;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.security.Principal; import java.security.Principal;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
/** /**
* @author Rob Winch * @author Rob Winch
*/ */
@RestController @RestController
public class RestDemoController { public class RestDemoController {
@RequestMapping(value="/",produces = "application/json") @RequestMapping(value="/",produces = "application/json")
public Map<String,String> helloUser(Principal principal) { public Map<String,String> helloUser(Principal principal) {
HashMap<String,String> result = new HashMap<String,String>(); HashMap<String,String> result = new HashMap<String,String>();
result.put("username", principal.getName()); result.put("username", principal.getName());
return result; return result;
} }
@RequestMapping("/logout") @RequestMapping("/logout")
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)
public void logout(HttpSession session) { public void logout(HttpSession session) {
session.invalidate(); session.invalidate();
} }
} }

View File

@@ -3,20 +3,20 @@ apply from: TOMCAT_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner { sonarRunner {
skipProject = true skipProject = true
} }
dependencies { dependencies {
compile project(':spring-session-data-redis'), compile project(':spring-session-data-redis'),
"org.springframework:spring-web:$springVersion", "org.springframework:spring-web:$springVersion",
"redis.embedded:embedded-redis:0.2", "redis.embedded:embedded-redis:0.2",
"org.springframework.security:spring-security-config:$springSecurityVersion", "org.springframework.security:spring-security-config:$springSecurityVersion",
"org.springframework.security:spring-security-web:$springSecurityVersion", "org.springframework.security:spring-security-web:$springSecurityVersion",
jstlDependencies jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
integrationTestCompile gebDependencies integrationTestCompile gebDependencies
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample
import geb.spock.GebReportingSpec import geb.spock.GebReportingSpec
@@ -14,37 +29,37 @@ import spock.lang.Stepwise
@Stepwise @Stepwise
class SecurityTests extends GebReportingSpec { class SecurityTests extends GebReportingSpec {
def 'Unauthenticated user sent to log in page'() { def 'Unauthenticated user sent to log in page'() {
when: 'unauthenticated user request protected page' when: 'unauthenticated user request protected page'
via HomePage via HomePage
then: 'sent to the log in page' then: 'sent to the log in page'
at LoginPage at LoginPage
} }
def 'Log in views home page'() { def 'Log in views home page'() {
when: 'log in successfully' when: 'log in successfully'
login() login()
then: 'sent to original page' then: 'sent to original page'
at HomePage at HomePage
and: 'the username is displayed' and: 'the username is displayed'
username == 'user' username == 'user'
and: 'Spring Session Management is being used' and: 'Spring Session Management is being used'
driver.manage().cookies.find { it.name == 'SESSION' } driver.manage().cookies.find { it.name == 'SESSION' }
and: 'Standard Session is NOT being used' and: 'Standard Session is NOT being used'
!driver.manage().cookies.find { it.name == 'JSESSIONID' } !driver.manage().cookies.find { it.name == 'JSESSIONID' }
} }
def 'Log out success'() { def 'Log out success'() {
when: when:
logout() logout()
then: then:
at LoginPage at LoginPage
} }
def 'Logged out user sent to log in page'() { def 'Logged out user sent to log in page'() {
when: 'logged out user request protected page' when: 'logged out user request protected page'
via HomePage via HomePage
then: 'sent to the log in page' then: 'sent to the log in page'
at LoginPage at LoginPage
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample.pages
import geb.Page import geb.Page
@@ -8,10 +23,10 @@ import geb.Page
* @author Rob Winch * @author Rob Winch
*/ */
class HomePage extends Page { class HomePage extends Page {
static url = '' static url = ''
static at = { assert driver.title == 'Secured Content'; true} static at = { assert driver.title == 'Secured Content'; true}
static content = { static content = {
username { $('#un').text() } username { $('#un').text() }
logout(to:LoginPage) { $('input[type=submit]').click() } logout(to:LoginPage) { $('input[type=submit]').click() }
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample.pages
import geb.Page import geb.Page
@@ -8,15 +23,15 @@ import geb.Page
* @author Rob Winch * @author Rob Winch
*/ */
class LoginPage extends Page { class LoginPage extends Page {
static url = '/login' static url = '/login'
static at = { assert driver.title == 'Login Page'; true} static at = { assert driver.title == 'Login Page'; true}
static content = { static content = {
form { $('form') } form { $('form') }
submit { $('input[type=submit]') } submit { $('input[type=submit]') }
login(required:false) { user='user', pass='password' -> login(required:false) { user='user', pass='password' ->
form.username = user form.username = user
form.password = pass form.password = pass
submit.click(HomePage) submit.click(HomePage)
} }
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* 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; package sample;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -6,13 +21,15 @@ import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
// tag::class[]
@Configuration @Configuration
@Import(EmbeddedRedisConfiguration.class) // <1> @Import(EmbeddedRedisConfiguration.class) // <1>
@EnableRedisHttpSession // <2> @EnableRedisHttpSession // <2>
public class Config { public class Config {
@Bean @Bean
public JedisConnectionFactory connectionFactory() { public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(); // <3> return new JedisConnectionFactory(); // <3>
} }
} }
// end::class[]

View File

@@ -1,6 +1,5 @@
package sample;
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -14,6 +13,8 @@ package sample;
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package sample;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@@ -36,35 +37,33 @@ import redis.embedded.RedisServer;
@Configuration @Configuration
public class EmbeddedRedisConfiguration { public class EmbeddedRedisConfiguration {
@Bean @Bean
public static RedisServerBean redisServer() { public static RedisServerBean redisServer() {
return new RedisServerBean(); return new RedisServerBean();
} }
/** /**
* Implements BeanDefinitionRegistryPostProcessor to ensure this Bean * Implements BeanDefinitionRegistryPostProcessor to ensure this Bean
* is initialized before any other Beans. Specifically, we want to ensure * is initialized before any other Beans. Specifically, we want to ensure
* that the Redis Server is started before RedisHttpSessionConfiguration * that the Redis Server is started before RedisHttpSessionConfiguration
* attempts to enable Keyspace notifications. * attempts to enable Keyspace notifications.
*/ */
static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor {
private RedisServer redisServer; private RedisServer redisServer;
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start(); redisServer.start();
} }
public void destroy() throws Exception { public void destroy() throws Exception {
if(redisServer != null) { if(redisServer != null) {
redisServer.stop(); redisServer.stop();
} }
} }
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} }
}
} }

View File

@@ -1,7 +1,24 @@
/*
* 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; package sample;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
// tag::class[]
public class Initializer extends AbstractHttpSessionApplicationInitializer { public class Initializer extends AbstractHttpSessionApplicationInitializer {
} }
// end::class[]

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -25,10 +25,10 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
@EnableWebSecurity @EnableWebSecurity
public class SecurityConfig { public class SecurityConfig {
@Autowired @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth auth
.inMemoryAuthentication() .inMemoryAuthentication()
.withUser("user").password("password").roles("USER"); .withUser("user").password("password").roles("USER");
} }
} }

View File

@@ -1,11 +1,28 @@
/*
* 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; package sample;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
// tag::class[]
public class SecurityInitializer extends public class SecurityInitializer extends
AbstractSecurityWebApplicationInitializer { AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() { public SecurityInitializer() {
super(SecurityConfig.class, Config.class); super(SecurityConfig.class, Config.class);
} }
} }
// end::class[]

View File

@@ -1,3 +1,18 @@
/*
* 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; package sample;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@@ -10,13 +25,13 @@ import java.io.IOException;
@WebServlet("/session") @WebServlet("/session")
public class SessionServlet extends HttpServlet { public class SessionServlet extends HttpServlet {
@Override @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 attributeName = req.getParameter("attributeName");
String attributeValue = req.getParameter("attributeValue"); String attributeValue = req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue); req.getSession().setAttribute(attributeName, attributeValue);
resp.sendRedirect(req.getContextPath() + "/"); resp.sendRedirect(req.getContextPath() + "/");
} }
private static final long serialVersionUID = 2878267318695777395L; private static final long serialVersionUID = 2878267318695777395L;
} }

View File

@@ -2,31 +2,31 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Secured Content</title> <title>Secured Content</title>
<link rel="stylesheet" href="assets/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap.min.css">
<style type="text/css"> <style type="text/css">
body { body {
padding: 1em; padding: 1em;
} }
#un { #un {
font-weight: bold; font-weight: bold;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>Description</h1> <h1>Description</h1>
<p>This demonstrates how Spring Session can be combined with Spring Security. The important thing to ensure is that Spring Session's Filter is included before Spring Security's Filter.</p> <p>This demonstrates how Spring Session can be combined with Spring Security. The important thing to ensure is that Spring Session's Filter is included before Spring Security's Filter.</p>
<h1>Logged in as</h1> <h1>Logged in as</h1>
<p>You are currently logged in as <span id="un"><c:out value="${pageContext.request.remoteUser}"/></span>.</p> <p>You are currently logged in as <span id="un"><c:out value="${pageContext.request.remoteUser}"/></span>.</p>
<c:url value="/logout" var="logoutUrl"/> <c:url value="/logout" var="logoutUrl"/>
<form action="${logoutUrl}" method="post"> <form action="${logoutUrl}" method="post">
<input type="submit" value="Log Out"/> <input type="submit" value="Log Out"/>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form> </form>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -3,18 +3,18 @@ apply from: TOMCAT_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner { sonarRunner {
skipProject = true skipProject = true
} }
dependencies { dependencies {
compile project(':spring-session-data-redis'), compile project(':spring-session-data-redis'),
"org.springframework:spring-web:$springVersion", "org.springframework:spring-web:$springVersion",
"redis.embedded:embedded-redis:0.2", "redis.embedded:embedded-redis:0.2",
jstlDependencies jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
integrationTestCompile gebDependencies integrationTestCompile gebDependencies
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample
import geb.spock.* import geb.spock.*
@@ -13,112 +28,112 @@ import pages.*
*/ */
@Stepwise @Stepwise
class UserTests extends GebReportingSpec { class UserTests extends GebReportingSpec {
def 'first visit not authenticated'() { def 'first visit not authenticated'() {
when: when:
to HomePage to HomePage
then: then:
form form
!username !username
} }
def 'invalid login'() { def 'invalid login'() {
setup: setup:
def user = 'rob' def user = 'rob'
when: when:
login(user, user+'invalid') login(user, user+'invalid')
then: then:
!username !username
error == 'Invalid username / password. Please ensure the username is the same as the password.' error == 'Invalid username / password. Please ensure the username is the same as the password.'
} }
def 'empty username'() { def 'empty username'() {
setup: setup:
def user = '' def user = ''
when: when:
login(user, user) login(user, user)
then: then:
!username !username
error == 'Invalid username / password. Please ensure the username is the same as the password.' error == 'Invalid username / password. Please ensure the username is the same as the password.'
} }
def 'login single user'() { def 'login single user'() {
setup: setup:
def user = 'rob' def user = 'rob'
when: when:
login(user, user) login(user, user)
then: then:
username == user username == user
} }
def 'add account'() { def 'add account'() {
when: when:
addAccount.click(HomePage) addAccount.click(HomePage)
then: then:
form form
!username !username
} }
def 'log in second user'() { def 'log in second user'() {
setup: setup:
def user = 'luke' def user = 'luke'
when: when:
login(user, user) login(user, user)
then: then:
username == user username == user
} }
def 'following links keeps new session'() { def 'following links keeps new session'() {
when: when:
navLink.click(LinkPage) navLink.click(LinkPage)
then: then:
username == 'luke' username == 'luke'
} }
def 'switch account rob'() { def 'switch account rob'() {
setup: setup:
def user = 'rob' def user = 'rob'
when: when:
switchAccount(user) switchAccount(user)
then: then:
username == user username == user
} }
def 'following links keeps original session'() { def 'following links keeps original session'() {
when: when:
navLink.click(LinkPage) navLink.click(LinkPage)
then: then:
username == 'rob' username == 'rob'
} }
def 'switch account luke'() { def 'switch account luke'() {
setup: setup:
def user = 'luke' def user = 'luke'
when: when:
switchAccount(user) switchAccount(user)
then: then:
username == user username == user
} }
def 'logout luke'() { def 'logout luke'() {
when: when:
logout.click(HomePage) logout.click(HomePage)
then: then:
!username !username
} }
def 'switch back rob'() { def 'switch back rob'() {
setup: setup:
def user = 'rob' def user = 'rob'
when: when:
switchAccount(user) switchAccount(user)
then: then:
username == user username == user
} }
def 'logout rob'() { def 'logout rob'() {
when: when:
logout.click(HomePage) logout.click(HomePage)
then: then:
!username !username
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample.pages
import geb.* import geb.*
@@ -8,24 +23,24 @@ import geb.*
* @author Rob Winch * @author Rob Winch
*/ */
class HomePage extends Page { class HomePage extends Page {
static url = '' static url = ''
static at = { assert driver.title == 'Demonstrates Multi User Log In'; true} static at = { assert driver.title == 'Demonstrates Multi User Log In'; true}
static content = { static content = {
navLink { $('#navLink') } navLink { $('#navLink') }
error { $('#error').text() } error { $('#error').text() }
form { $('form') } form { $('form') }
username(required:false) { $('#un').text() } username(required:false) { $('#un').text() }
logout(required:false) { $('#logout') } logout(required:false) { $('#logout') }
addAccount(required:false) { $('#addAccount') } addAccount(required:false) { $('#addAccount') }
submit { $('input[type=submit]') } submit { $('input[type=submit]') }
login(required:false) { user, pass -> login(required:false) { user, pass ->
form.username = user form.username = user
form.password = pass form.password = pass
submit.click(HomePage) submit.click(HomePage)
} }
switchAccount{ un -> switchAccount{ un ->
$("#switchAccount${un}").click(HomePage) $("#switchAccount${un}").click(HomePage)
} }
attributes { moduleList AttributeRow, $("table tr").tail() } attributes { moduleList AttributeRow, $("table tr").tail() }
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* 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 package sample.pages
import geb.* import geb.*
@@ -8,13 +23,13 @@ import geb.*
* @author Rob Winch * @author Rob Winch
*/ */
class LinkPage extends Page { class LinkPage extends Page {
static url = '' static url = ''
static at = { assert driver.title == 'Linked Page'; true} static at = { assert driver.title == 'Linked Page'; true}
static content = { static content = {
form { $('#navLinks') } form { $('#navLinks') }
username(required:false) { $('#un').text() } username(required:false) { $('#un').text() }
switchAccount{ un -> switchAccount{ un ->
$("#switchAccount${un}").click(HomePage) $("#switchAccount${un}").click(HomePage)
} }
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,29 +16,29 @@
package sample; package sample;
public class Account { public class Account {
private String username; private String username;
private String logoutUrl; private String logoutUrl;
private String switchAccountUrl; private String switchAccountUrl;
public Account(String username, String logoutUrl, String switchAccountUrl) { public Account(String username, String logoutUrl, String switchAccountUrl) {
super(); super();
this.username = username; this.username = username;
this.logoutUrl = logoutUrl; this.logoutUrl = logoutUrl;
this.switchAccountUrl = switchAccountUrl; this.switchAccountUrl = switchAccountUrl;
} }
public String getUsername() { public String getUsername() {
return username; return username;
} }
public String getLogoutUrl() { public String getLogoutUrl() {
return logoutUrl; return logoutUrl;
} }
public String getSwitchAccountUrl() { public String getSwitchAccountUrl() {
return switchAccountUrl; return switchAccountUrl;
} }
} }

View File

@@ -1,6 +1,5 @@
package sample;
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -14,6 +13,7 @@ package sample;
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package sample;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -24,13 +24,15 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableR
/** /**
* @author Rob Winch * @author Rob Winch
*/ */
// tag::class[]
@Import(EmbeddedRedisConfiguration.class) @Import(EmbeddedRedisConfiguration.class)
@Configuration @Configuration
@EnableRedisHttpSession @EnableRedisHttpSession
public class Config { public class Config {
@Bean @Bean
public JedisConnectionFactory connectionFactory() { public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(); return new JedisConnectionFactory();
} }
} }
// end::class[]

View File

@@ -1,6 +1,5 @@
package sample;
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -14,6 +13,8 @@ package sample;
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package sample;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@@ -36,36 +37,34 @@ import redis.embedded.RedisServer;
@Configuration @Configuration
public class EmbeddedRedisConfiguration { public class EmbeddedRedisConfiguration {
@Bean @Bean
public RedisServerBean redisServer() { public RedisServerBean redisServer() {
return new RedisServerBean(); return new RedisServerBean();
} }
/** /**
* Implements BeanDefinitionRegistryPostProcessor to ensure this Bean * Implements BeanDefinitionRegistryPostProcessor to ensure this Bean
* is initialized before any other Beans. Specifically, we want to ensure * is initialized before any other Beans. Specifically, we want to ensure
* that the Redis Server is started before RedisHttpSessionConfiguration * that the Redis Server is started before RedisHttpSessionConfiguration
* attempts to enable Keyspace notifications. * attempts to enable Keyspace notifications.
*/ */
class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor {
private RedisServer redisServer; private RedisServer redisServer;
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start(); redisServer.start();
} }
public void destroy() throws Exception { public void destroy() throws Exception {
if(redisServer != null) { if(redisServer != null) {
redisServer.stop(); redisServer.stop();
} }
} }
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} }
}
} }

View File

@@ -1,6 +1,5 @@
package sample;
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -14,7 +13,7 @@ package sample;
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package sample;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
@@ -25,12 +24,12 @@ import org.springframework.session.web.context.AbstractHttpSessionApplicationIni
*/ */
public class Initializer extends AbstractHttpSessionApplicationInitializer { public class Initializer extends AbstractHttpSessionApplicationInitializer {
public Initializer() { public Initializer() {
super(Config.class); super(Config.class);
} }
@Override @Override
protected void afterSessionRepositoryFilter(ServletContext servletContext) { protected void afterSessionRepositoryFilter(ServletContext servletContext) {
appendFilters(servletContext, new UserAccountsFilter()); appendFilters(servletContext, new UserAccountsFilter());
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,21 +26,21 @@ import javax.servlet.http.HttpServletResponse;
@WebServlet("/login") @WebServlet("/login")
public class LoginServlet extends HttpServlet { public class LoginServlet extends HttpServlet {
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { throws ServletException, IOException {
String username = req.getParameter("username"); String username = req.getParameter("username");
String password = req.getParameter("password"); String password = req.getParameter("password");
if(username != null && !"".equals(username) && username.equals(password)) { if(username != null && !"".equals(username) && username.equals(password)) {
req.getSession().setAttribute("username", username); req.getSession().setAttribute("username", username);
String url = resp.encodeRedirectURL(req.getContextPath() + "/"); String url = resp.encodeRedirectURL(req.getContextPath() + "/");
resp.sendRedirect(url); resp.sendRedirect(url);
} else { } else {
String url = resp.encodeRedirectURL(req.getContextPath() + "/?error"); String url = resp.encodeRedirectURL(req.getContextPath() + "/?error");
resp.sendRedirect(url); resp.sendRedirect(url);
} }
} }
private static final long serialVersionUID = -8157634860354132501L; private static final long serialVersionUID = -8157634860354132501L;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,16 +27,16 @@ import javax.servlet.http.HttpSession;
@WebServlet("/logout") @WebServlet("/logout")
public class LogoutServlet extends HttpServlet { public class LogoutServlet extends HttpServlet {
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { throws ServletException, IOException {
HttpSession session = req.getSession(false); HttpSession session = req.getSession(false);
if(session != null) { if(session != null) {
session.invalidate(); session.invalidate();
} }
String url = resp.encodeRedirectURL(req.getContextPath() + "/"); String url = resp.encodeRedirectURL(req.getContextPath() + "/");
resp.sendRedirect(url); resp.sendRedirect(url);
} }
private static final long serialVersionUID = 4061762524521437433L; private static final long serialVersionUID = 4061762524521437433L;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -34,68 +34,68 @@ import org.springframework.session.web.http.HttpSessionManager;
public class UserAccountsFilter implements Filter { public class UserAccountsFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException { public void init(FilterConfig filterConfig) throws ServletException {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void doFilter(ServletRequest request, ServletResponse response, public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException { FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletRequest httpRequest = (HttpServletRequest) request;
// tag::HttpSessionManager[] // tag::HttpSessionManager[]
HttpSessionManager sessionManager = HttpSessionManager sessionManager =
(HttpSessionManager) httpRequest.getAttribute(HttpSessionManager.class.getName()); (HttpSessionManager) httpRequest.getAttribute(HttpSessionManager.class.getName());
// end::HttpSessionManager[] // end::HttpSessionManager[]
SessionRepository<Session> repo = SessionRepository<Session> repo =
(SessionRepository<Session>) httpRequest.getAttribute(SessionRepository.class.getName()); (SessionRepository<Session>) httpRequest.getAttribute(SessionRepository.class.getName());
String currentSessionAlias = sessionManager.getCurrentSessionAlias(httpRequest); String currentSessionAlias = sessionManager.getCurrentSessionAlias(httpRequest);
Map<String, String> sessionIds = sessionManager.getSessionIds(httpRequest); Map<String, String> sessionIds = sessionManager.getSessionIds(httpRequest);
String unauthenticatedAlias = null; String unauthenticatedAlias = null;
String contextPath = httpRequest.getContextPath(); String contextPath = httpRequest.getContextPath();
List<Account> accounts = new ArrayList<Account>(); List<Account> accounts = new ArrayList<Account>();
Account currentAccount = null; Account currentAccount = null;
for(Map.Entry<String, String> entry : sessionIds.entrySet()) { for(Map.Entry<String, String> entry : sessionIds.entrySet()) {
String alias = entry.getKey(); String alias = entry.getKey();
String sessionId = entry.getValue(); String sessionId = entry.getValue();
Session session = repo.getSession(sessionId); Session session = repo.getSession(sessionId);
if(session == null) { if(session == null) {
continue; continue;
} }
String username = session.getAttribute("username"); String username = session.getAttribute("username");
if(username == null) { if(username == null) {
unauthenticatedAlias = alias; unauthenticatedAlias = alias;
continue; continue;
} }
String logoutUrl = sessionManager.encodeURL("./logout", alias); String logoutUrl = sessionManager.encodeURL("./logout", alias);
String switchAccountUrl = sessionManager.encodeURL("./", alias); String switchAccountUrl = sessionManager.encodeURL("./", alias);
Account account = new Account(username, logoutUrl, switchAccountUrl); Account account = new Account(username, logoutUrl, switchAccountUrl);
if(currentSessionAlias.equals(alias)) { if(currentSessionAlias.equals(alias)) {
currentAccount = account; currentAccount = account;
} else { } else {
accounts.add(account); accounts.add(account);
} }
} }
// tag::addAccountUrl[] // tag::addAccountUrl[]
String addAlias = unauthenticatedAlias == null ? // <1> String addAlias = unauthenticatedAlias == null ? // <1>
sessionManager.getNewSessionAlias(httpRequest) : // <2> sessionManager.getNewSessionAlias(httpRequest) : // <2>
unauthenticatedAlias; // <3> unauthenticatedAlias; // <3>
String addAccountUrl = sessionManager.encodeURL(contextPath, addAlias); // <4> String addAccountUrl = sessionManager.encodeURL(contextPath, addAlias); // <4>
// end::addAccountUrl[] // end::addAccountUrl[]
httpRequest.setAttribute("currentAccount", currentAccount); httpRequest.setAttribute("currentAccount", currentAccount);
httpRequest.setAttribute("addAccountUrl", addAccountUrl); httpRequest.setAttribute("addAccountUrl", addAccountUrl);
httpRequest.setAttribute("accounts", accounts); httpRequest.setAttribute("accounts", accounts);
chain.doFilter(request, response); chain.doFilter(request, response);
} }
public void destroy() { public void destroy() {
} }
} }

View File

@@ -1,10 +1,10 @@
buildscript { buildscript {
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
} }
} }
apply plugin: 'spring-boot' apply plugin: 'spring-boot'
@@ -17,23 +17,23 @@ tasks.findByPath("artifactoryPublish")?.enabled = false
group = 'samples' group = 'samples'
dependencies { dependencies {
compile project(':spring-session-data-redis'), compile project(':spring-session-data-redis'),
"org.springframework.boot:spring-boot-starter-web", "org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-data-jpa", "org.springframework.boot:spring-boot-starter-data-jpa",
"org.springframework.boot:spring-boot-starter-thymeleaf", "org.springframework.boot:spring-boot-starter-thymeleaf",
"org.springframework.boot:spring-boot-starter-websocket", "org.springframework.boot:spring-boot-starter-websocket",
"org.springframework:spring-websocket:${springVersion}", "org.springframework:spring-websocket:${springVersion}",
"org.springframework.data:spring-data-jpa:1.7.0.RELEASE", "org.springframework.data:spring-data-jpa:1.7.0.RELEASE",
"redis.embedded:embedded-redis:0.2", "redis.embedded:embedded-redis:0.2",
"nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect", "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect",
"com.h2database:h2", "com.h2database:h2",
"org.springframework.security:spring-security-web:$springSecurityVersion", "org.springframework.security:spring-security-web:$springSecurityVersion",
"org.springframework.security:spring-security-config:$springSecurityVersion", "org.springframework.security:spring-security-config:$springSecurityVersion",
"org.springframework.security:spring-security-messaging:$springSecurityVersion", "org.springframework.security:spring-security-messaging:$springSecurityVersion",
"org.springframework.security:spring-security-data:$springSecurityVersion" "org.springframework.security:spring-security-data:$springSecurityVersion"
testCompile "org.springframework.boot:spring-boot-starter-test", testCompile "org.springframework.boot:spring-boot-starter-test",
"org.springframework.security:spring-security-test:$springSecurityVersion" "org.springframework.security:spring-security-test:$springSecurityVersion"
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration;
@EnableAutoConfiguration @EnableAutoConfiguration
public class Application { public class Application {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* 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; package sample.config;
import javax.sql.DataSource; import javax.sql.DataSource;
@@ -11,14 +26,14 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@Configuration @Configuration
public class DataSourceConfig { public class DataSourceConfig {
@Bean @Bean
public DataSource dataSource() { public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.H2).build(); return builder.setType(EmbeddedDatabaseType.H2).build();
} }
@Bean @Bean
public JedisConnectionFactory connectionFactory() throws Exception { public JedisConnectionFactory connectionFactory() throws Exception {
return new JedisConnectionFactory(); return new JedisConnectionFactory();
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -33,36 +33,34 @@ import redis.embedded.RedisServer;
@Configuration @Configuration
public class EmbeddedRedisConfig { public class EmbeddedRedisConfig {
@Bean @Bean
public static RedisServerBean redisServer() { public static RedisServerBean redisServer() {
return new RedisServerBean(); return new RedisServerBean();
} }
/** /**
* Implements BeanDefinitionRegistryPostProcessor to ensure this Bean * Implements BeanDefinitionRegistryPostProcessor to ensure this Bean
* is initialized before any other Beans. Specifically, we want to ensure * is initialized before any other Beans. Specifically, we want to ensure
* that the Redis Server is started before RedisHttpSessionConfiguration * that the Redis Server is started before RedisHttpSessionConfiguration
* attempts to enable Keyspace notifications. * attempts to enable Keyspace notifications.
*/ */
static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor {
private RedisServer redisServer; private RedisServer redisServer;
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start(); redisServer.start();
} }
public void destroy() throws Exception { public void destroy() throws Exception {
if(redisServer != null) { if(redisServer != null) {
redisServer.stop(); redisServer.stop();
} }
} }
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} }
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ public class H2Initializer {
@Bean @Bean
public ServletRegistrationBean h2Servlet() { public ServletRegistrationBean h2Servlet() {
ServletRegistrationBean servletBean = new ServletRegistrationBean(); ServletRegistrationBean servletBean = new ServletRegistrationBean();
servletBean.addUrlMappings("/h2/*"); servletBean.addUrlMappings("/h2/*");
servletBean.setServlet(new WebServlet()); servletBean.setServlet(new WebServlet());
return servletBean; return servletBean;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -34,31 +34,34 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableR
// tag::enable-redis-httpsession[] // tag::enable-redis-httpsession[]
@EnableRedisHttpSession//(maxInactiveIntervalInSeconds = 60) @EnableRedisHttpSession//(maxInactiveIntervalInSeconds = 60)
public class WebSecurityConfig public class WebSecurityConfig
extends WebSecurityConfigurerAdapter { extends WebSecurityConfigurerAdapter {
// end::enable-redis-httpsession[] // end::enable-redis-httpsession[]
@Override // @formatter:off
protected void configure(HttpSecurity http) throws Exception { @Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout()
.permitAll();
}
// @formatter:on
http // @formatter:off
.authorizeRequests() @Autowired
.anyRequest().authenticated() public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService userDetailsService) throws Exception {
.and() auth
.formLogin() .userDetailsService(userDetailsService)
.and() .passwordEncoder(new BCryptPasswordEncoder());
.logout() }
.permitAll(); // @formatter:on
}
@Autowired @Bean
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService userDetailsService) throws Exception { public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
auth return new SecurityEvaluationContextExtension();
.userDetailsService(userDetailsService) }
.passwordEncoder(new BCryptPasswordEncoder());
}
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -15,7 +15,6 @@
*/ */
package sample.config; package sample.config;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
@@ -29,16 +28,16 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@EnableScheduling @EnableScheduling
@EnableWebSocketMessageBroker @EnableWebSocketMessageBroker
public class WebSocketConfig public class WebSocketConfig
extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> { // <1> extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> { // <1>
protected void configureStompEndpoints(StompEndpointRegistry registry) { // <2> protected void configureStompEndpoints(StompEndpointRegistry registry) { // <2>
registry.addEndpoint("/messages") registry.addEndpoint("/messages")
.withSockJS(); .withSockJS();
} }
public void configureMessageBroker(MessageBrokerRegistry registry) { public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/"); registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app"); registry.setApplicationDestinationPrefixes("/app");
} }
} }
// end::class[] // end::class[]

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -31,13 +31,13 @@ import sample.websocket.WebSocketDisconnectHandler;
@Configuration @Configuration
public class WebSocketHandlersConfig<S extends ExpiringSession> { public class WebSocketHandlersConfig<S extends ExpiringSession> {
@Bean @Bean
public WebSocketConnectHandler<S> webSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) { public WebSocketConnectHandler<S> webSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
return new WebSocketConnectHandler<S>(messagingTemplate, repository); return new WebSocketConnectHandler<S>(messagingTemplate, repository);
} }
@Bean @Bean
public WebSocketDisconnectHandler<S> webSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) { public WebSocketDisconnectHandler<S> webSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
return new WebSocketDisconnectHandler<S>(messagingTemplate, repository); return new WebSocketDisconnectHandler<S>(messagingTemplate, repository);
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -26,11 +26,13 @@ import org.springframework.security.config.annotation.web.socket.AbstractSecurit
@Configuration @Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override // @formatter:off
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { @Override
messages protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
.antMatchers(SimpMessageType.MESSAGE,"/queue/**","/topic/**").denyAll() messages
.antMatchers(SimpMessageType.SUBSCRIBE, "/queue/**/*-user*","/topic/**/*-user*").denyAll() .antMatchers(SimpMessageType.MESSAGE,"/queue/**","/topic/**").denyAll()
.anyMessage().authenticated(); .antMatchers(SimpMessageType.SUBSCRIBE, "/queue/**/*-user*","/topic/**/*-user*").denyAll()
} .anyMessage().authenticated();
}
// @formatter:on
} }

View File

@@ -1,6 +1,20 @@
/*
* 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.data; package sample.data;
import java.util.Calendar; import java.util.Calendar;
import javax.persistence.Entity; import javax.persistence.Entity;
@@ -10,13 +24,13 @@ import javax.persistence.Id;
public class ActiveWebSocketUser { public class ActiveWebSocketUser {
@Id @Id
private String id; private String id;
private String username; private String username;
private Calendar connectionTime; private Calendar connectionTime;
public ActiveWebSocketUser() {} public ActiveWebSocketUser() {}
public ActiveWebSocketUser(String id, String username, Calendar connectionTime) { public ActiveWebSocketUser(String id, String username, Calendar connectionTime) {
super(); super();
this.id = id; this.id = id;
@@ -39,6 +53,6 @@ public class ActiveWebSocketUser {
public void setConnectionTime(Calendar connectionTime) { public void setConnectionTime(Calendar connectionTime) {
this.connectionTime = connectionTime; this.connectionTime = connectionTime;
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* 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.data; package sample.data;
import java.util.List; import java.util.List;

View File

@@ -1,14 +1,29 @@
/*
* 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.data; package sample.data;
import java.util.Calendar; import java.util.Calendar;
public class InstantMessage { public class InstantMessage {
private String to; private String to;
private String from; private String from;
private String message; private String message;
private Calendar created = Calendar.getInstance(); private Calendar created = Calendar.getInstance();
public String getTo() { public String getTo() {
@@ -42,7 +57,7 @@ public class InstantMessage {
public void setCreated(Calendar created) { public void setCreated(Calendar created) {
this.created = created; this.created = created;
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -40,73 +40,73 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@Entity @Entity
public class User implements Serializable { public class User implements Serializable {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
private Long id; private Long id;
@NotEmpty(message = "First name is required.") @NotEmpty(message = "First name is required.")
private String firstName; private String firstName;
@NotEmpty(message = "Last name is required.") @NotEmpty(message = "Last name is required.")
private String lastName; private String lastName;
@Email(message = "Please provide a valid email address.") @Email(message = "Please provide a valid email address.")
@NotEmpty(message = "Email is required.") @NotEmpty(message = "Email is required.")
@Column(unique=true, nullable = false) @Column(unique=true, nullable = false)
private String email; private String email;
@NotEmpty(message = "Password is required.") @NotEmpty(message = "Password is required.")
private String password; private String password;
public User() {} public User() {}
public User(User user) { public User(User user) {
this.id = user.id; this.id = user.id;
this.firstName = user.firstName; this.firstName = user.firstName;
this.lastName = user.lastName; this.lastName = user.lastName;
this.email = user.email; this.email = user.email;
this.password = user.password; this.password = user.password;
} }
public String getPassword() { public String getPassword() {
return password; return password;
} }
public void setPassword(String password) { public void setPassword(String password) {
this.password = password; this.password = password;
} }
public Long getId() { public Long getId() {
return id; return id;
} }
public void setId(Long id) { public void setId(Long id) {
this.id = id; this.id = id;
} }
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;
} }
public void setFirstName(String firstName) { public void setFirstName(String firstName) {
this.firstName = firstName; this.firstName = firstName;
} }
public String getLastName() { public String getLastName() {
return lastName; return lastName;
} }
public void setLastName(String lastName) { public void setLastName(String lastName) {
this.lastName = lastName; this.lastName = lastName;
} }
public String getEmail() { public String getEmail() {
return email; return email;
} }
public void setEmail(String email) { public void setEmail(String email) {
this.email = email; this.email = email;
} }
private static final long serialVersionUID = 2738859149330833739L; private static final long serialVersionUID = 2738859149330833739L;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -25,5 +25,5 @@ import org.springframework.data.repository.CrudRepository;
*/ */
public interface UserRepository extends CrudRepository<User, Long> { public interface UserRepository extends CrudRepository<User, Long> {
User findByEmail(String email); User findByEmail(String email);
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -22,7 +22,6 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -40,29 +39,29 @@ import sample.security.CurrentUser;
@Controller @Controller
@RequestMapping("/") @RequestMapping("/")
public class MessageController { public class MessageController {
private SimpMessageSendingOperations messagingTemplate; private SimpMessageSendingOperations messagingTemplate;
private ActiveWebSocketUserRepository activeUserRepository; private ActiveWebSocketUserRepository activeUserRepository;
@Autowired @Autowired
public MessageController(ActiveWebSocketUserRepository activeUserRepository,SimpMessageSendingOperations messagingTemplate) { public MessageController(ActiveWebSocketUserRepository activeUserRepository,SimpMessageSendingOperations messagingTemplate) {
this.activeUserRepository = activeUserRepository; this.activeUserRepository = activeUserRepository;
this.messagingTemplate = messagingTemplate; this.messagingTemplate = messagingTemplate;
} }
@RequestMapping("/") @RequestMapping("/")
public String im() { public String im() {
return "index"; return "index";
} }
@MessageMapping("/im") @MessageMapping("/im")
public void im(InstantMessage im, @CurrentUser User currentUser) { public void im(InstantMessage im, @CurrentUser User currentUser) {
im.setFrom(currentUser.getEmail()); im.setFrom(currentUser.getEmail());
messagingTemplate.convertAndSendToUser(im.getTo(),"/queue/messages",im); messagingTemplate.convertAndSendToUser(im.getTo(),"/queue/messages",im);
messagingTemplate.convertAndSendToUser(im.getFrom(),"/queue/messages",im); messagingTemplate.convertAndSendToUser(im.getFrom(),"/queue/messages",im);
} }
@SubscribeMapping("/users") @SubscribeMapping("/users")
public List<String> subscribeMessages() throws Exception { public List<String> subscribeMessages() throws Exception {
return activeUserRepository.findAllActiveUsers(); return activeUserRepository.findAllActiveUsers();
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -33,55 +33,55 @@ import org.springframework.stereotype.Service;
*/ */
@Service @Service
public class UserRepositoryUserDetailsService implements UserDetailsService { public class UserRepositoryUserDetailsService implements UserDetailsService {
private final UserRepository userRepository; private final UserRepository userRepository;
@Autowired @Autowired
public UserRepositoryUserDetailsService(UserRepository userRepository) { public UserRepositoryUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository; this.userRepository = userRepository;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String) * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/ */
public UserDetails loadUserByUsername(String username) public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException { throws UsernameNotFoundException {
User user = userRepository.findByEmail(username); User user = userRepository.findByEmail(username);
if(user == null) { if(user == null) {
throw new UsernameNotFoundException("Could not find user " + username); throw new UsernameNotFoundException("Could not find user " + username);
} }
return new CustomUserDetails(user); return new CustomUserDetails(user);
} }
private final static class CustomUserDetails extends User implements UserDetails { private final static class CustomUserDetails extends User implements UserDetails {
private CustomUserDetails(User user) { private CustomUserDetails(User user) {
super(user); super(user);
} }
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList("ROLE_USER"); return AuthorityUtils.createAuthorityList("ROLE_USER");
} }
public String getUsername() { public String getUsername() {
return getEmail(); return getEmail();
} }
public boolean isAccountNonExpired() { public boolean isAccountNonExpired() {
return true; return true;
} }
public boolean isAccountNonLocked() { public boolean isAccountNonLocked() {
return true; return true;
} }
public boolean isCredentialsNonExpired() { public boolean isCredentialsNonExpired() {
return true; return true;
} }
public boolean isEnabled() { public boolean isEnabled() {
return true; return true;
} }
private static final long serialVersionUID = 5639683223516504866L; private static final long serialVersionUID = 5639683223516504866L;
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -29,23 +29,23 @@ import sample.data.ActiveWebSocketUser;
import sample.data.ActiveWebSocketUserRepository; import sample.data.ActiveWebSocketUserRepository;
public class WebSocketConnectHandler<S> implements ApplicationListener<SessionConnectEvent> { public class WebSocketConnectHandler<S> implements ApplicationListener<SessionConnectEvent> {
private ActiveWebSocketUserRepository repository; private ActiveWebSocketUserRepository repository;
private SimpMessageSendingOperations messagingTemplate; private SimpMessageSendingOperations messagingTemplate;
public WebSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) { public WebSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
super(); super();
this.messagingTemplate = messagingTemplate; this.messagingTemplate = messagingTemplate;
this.repository = repository; this.repository = repository;
} }
public void onApplicationEvent(SessionConnectEvent event) { public void onApplicationEvent(SessionConnectEvent event) {
MessageHeaders headers = event.getMessage().getHeaders(); MessageHeaders headers = event.getMessage().getHeaders();
Principal user = SimpMessageHeaderAccessor.getUser(headers); Principal user = SimpMessageHeaderAccessor.getUser(headers);
if(user == null) { if(user == null) {
return; return;
} }
String id = SimpMessageHeaderAccessor.getSessionId(headers); String id = SimpMessageHeaderAccessor.getSessionId(headers);
repository.save(new ActiveWebSocketUser(id, user.getName(), Calendar.getInstance())); repository.save(new ActiveWebSocketUser(id, user.getName(), Calendar.getInstance()));
messagingTemplate.convertAndSend("/topic/friends/signin", Arrays.asList(user.getName())); messagingTemplate.convertAndSend("/topic/friends/signin", Arrays.asList(user.getName()));
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -25,27 +25,27 @@ import sample.data.ActiveWebSocketUser;
import sample.data.ActiveWebSocketUserRepository; import sample.data.ActiveWebSocketUserRepository;
public class WebSocketDisconnectHandler<S> implements ApplicationListener<SessionDisconnectEvent> { public class WebSocketDisconnectHandler<S> implements ApplicationListener<SessionDisconnectEvent> {
private ActiveWebSocketUserRepository repository; private ActiveWebSocketUserRepository repository;
private SimpMessageSendingOperations messagingTemplate; private SimpMessageSendingOperations messagingTemplate;
public WebSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) { public WebSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
super(); super();
this.messagingTemplate = messagingTemplate; this.messagingTemplate = messagingTemplate;
this.repository = repository; this.repository = repository;
} }
public void onApplicationEvent(SessionDisconnectEvent event) { public void onApplicationEvent(SessionDisconnectEvent event) {
String id = event.getSessionId(); String id = event.getSessionId();
if(id == null) { if(id == null) {
return; return;
} }
ActiveWebSocketUser user = repository.findOne(id); ActiveWebSocketUser user = repository.findOne(id);
if(user == null) { if(user == null) {
return; return;
} }
repository.delete(id); repository.delete(id);
messagingTemplate.convertAndSend("/topic/friends/signout", Arrays.asList(user.getUsername())); messagingTemplate.convertAndSend("/topic/friends/signout", Arrays.asList(user.getUsername()));
} }
} }

View File

@@ -1,139 +1,139 @@
function ApplicationModel(stompClient) { function ApplicationModel(stompClient) {
var self = this; var self = this;
self.friends = ko.observableArray(); self.friends = ko.observableArray();
self.username = ko.observable(); self.username = ko.observable();
self.conversation = ko.observable(new ImConversationModel(stompClient,this.username)); self.conversation = ko.observable(new ImConversationModel(stompClient,this.username));
self.notifications = ko.observableArray(); self.notifications = ko.observableArray();
self.connect = function() { self.connect = function() {
stompClient.connect({}, function(frame) { stompClient.connect({}, function(frame) {
console.log('Connected ' + frame); console.log('Connected ' + frame);
self.username(frame.headers['user-name']); self.username(frame.headers['user-name']);
// self.friendSignin({"username": "luke"}); // self.friendSignin({"username": "luke"});
stompClient.subscribe("/user/queue/errors", function(message) { stompClient.subscribe("/user/queue/errors", function(message) {
self.pushNotification("Error " + message.body); self.pushNotification("Error " + message.body);
}); });
stompClient.subscribe("/app/users", function(message) { stompClient.subscribe("/app/users", function(message) {
var friends = JSON.parse(message.body); var friends = JSON.parse(message.body);
for(var i=0;i<friends.length;i++) { for(var i=0;i<friends.length;i++) {
self.friendSignin({"username": friends[i]}); self.friendSignin({"username": friends[i]});
} }
}); });
stompClient.subscribe("/topic/friends/signin", function(message) { stompClient.subscribe("/topic/friends/signin", function(message) {
var friends = JSON.parse(message.body); var friends = JSON.parse(message.body);
for(var i=0;i<friends.length;i++) { for(var i=0;i<friends.length;i++) {
self.friendSignin(new ImFriend({"username": friends[i]})); self.friendSignin(new ImFriend({"username": friends[i]}));
} }
}); });
stompClient.subscribe("/topic/friends/signout", function(message) { stompClient.subscribe("/topic/friends/signout", function(message) {
var friends = JSON.parse(message.body); var friends = JSON.parse(message.body);
for(var i=0;i<friends.length;i++) { for(var i=0;i<friends.length;i++) {
self.friendSignout(new ImFriend({"username": friends[i]})); self.friendSignout(new ImFriend({"username": friends[i]}));
} }
}); });
stompClient.subscribe("/user/queue/messages", function(message) { stompClient.subscribe("/user/queue/messages", function(message) {
self.conversation().receiveMessage(JSON.parse(message.body)); self.conversation().receiveMessage(JSON.parse(message.body));
}); });
}, function(error) { }, function(error) {
self.pushNotification(error) self.pushNotification(error)
console.log("STOMP protocol error " + error); console.log("STOMP protocol error " + error);
}); });
} }
self.pushNotification = function(text) { self.pushNotification = function(text) {
self.notifications.push({notification: text}); self.notifications.push({notification: text});
if (self.notifications().length > 5) { if (self.notifications().length > 5) {
self.notifications.shift(); self.notifications.shift();
} }
} }
self.logout = function() { self.logout = function() {
stompClient.disconnect(); stompClient.disconnect();
window.location.href = "../logout.html"; window.location.href = "../logout.html";
} }
self.friendSignin = function(friend) { self.friendSignin = function(friend) {
self.friends.push(friend); self.friends.push(friend);
} }
self.friendSignout = function(friend) { self.friendSignout = function(friend) {
var r = self.friends.remove( var r = self.friends.remove(
function(item) { function(item) {
item.username == friend.username item.username == friend.username
} }
); );
self.friends(r); self.friends(r);
} }
} }
function ImFriend(data) { function ImFriend(data) {
var self = this; var self = this;
self.username = data.username; self.username = data.username;
} }
function ImConversationModel(stompClient,from) { function ImConversationModel(stompClient,from) {
var self = this; var self = this;
self.stompClient = stompClient; self.stompClient = stompClient;
self.from = from; self.from = from;
self.to = ko.observable(new ImFriend('null')); self.to = ko.observable(new ImFriend('null'));
self.draft = ko.observable('') self.draft = ko.observable('')
self.messages = ko.observableArray(); self.messages = ko.observableArray();
self.receiveMessage = function(message) { self.receiveMessage = function(message) {
var elem = $('#chat'); var elem = $('#chat');
var isFromSelf = self.from() == message.from; var isFromSelf = self.from() == message.from;
var isFromTo = self.to().username == message.from; var isFromTo = self.to().username == message.from;
if(!(isFromTo || isFromSelf)) { if(!(isFromTo || isFromSelf)) {
self.chat(new ImFriend({"username":message.from})) self.chat(new ImFriend({"username":message.from}))
} }
var atBottom = (elem[0].scrollHeight - elem.scrollTop() == elem.outerHeight()); var atBottom = (elem[0].scrollHeight - elem.scrollTop() == elem.outerHeight());
self.messages.push(new ImModel(message)); self.messages.push(new ImModel(message));
if (atBottom) if (atBottom)
elem.scrollTop(elem[0].scrollHeight); elem.scrollTop(elem[0].scrollHeight);
}; };
self.chat = function(to) { self.chat = function(to) {
self.to(to); self.to(to);
self.draft(''); self.draft('');
self.messages.removeAll() self.messages.removeAll()
$('#trade-dialog').modal(); $('#trade-dialog').modal();
} }
self.send = function() { self.send = function() {
var data = { var data = {
"created" : new Date(), "created" : new Date(),
"from" : self.from(), "from" : self.from(),
"to" : self.to().username, "to" : self.to().username,
"message" : self.draft() "message" : self.draft()
}; };
var destination = "/app/im"; // /queue/messages-user1 var destination = "/app/im"; // /queue/messages-user1
stompClient.send(destination, {}, JSON.stringify(data)); stompClient.send(destination, {}, JSON.stringify(data));
self.draft(''); self.draft('');
} }
}; };
function ImModel(data) { function ImModel(data) {
var self = this; var self = this;
self.created = new Date(data.created); self.created = new Date(data.created);
self.to = data.to; self.to = data.to;
self.message = data.message; self.message = data.message;
self.from = data.from; self.from = data.from;
self.messageFormatted = ko.computed(function() { self.messageFormatted = ko.computed(function() {
return self.created.getHours() + ":" + self.created.getMinutes() + ":" + self.created.getSeconds() + " - " + self.from + " - " + self.message; return self.created.getHours() + ":" + self.created.getMinutes() + ":" + self.created.getSeconds() + " - " + self.from + " - " + self.message;
}) })
}; };

View File

@@ -1,74 +1,74 @@
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout"> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
<head> <head>
<title>View All</title> <title>View All</title>
</head> </head>
<body> <body>
<div layout:fragment="content"> <div layout:fragment="content">
<div class="container"> <div class="container">
<div id="heading" class="masthead"> <div id="heading" class="masthead">
<h3 class="muted">Chat Application</h3> <h3 class="muted">Chat Application</h3>
</div> </div>
<div id="main-content"> <div id="main-content">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>User</th> <th>User</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody data-bind="foreach: friends()"> <tbody data-bind="foreach: friends()">
<tr> <tr>
<td data-bind="text: username"></td> <td data-bind="text: username"></td>
<td class="trade-buttons"> <td class="trade-buttons">
<button class="btn btn-primary" data-bind="click: $root.conversation().chat">Chat</button> <button class="btn btn-primary" data-bind="click: $root.conversation().chat">Chat</button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div id="trade-dialog" class="modal hide fade" tabindex="-1"> <div id="trade-dialog" class="modal hide fade" tabindex="-1">
<div class="modal-body"> <div class="modal-body">
<div>Chat with <span data-bind="text: conversation().to().username"></span></div> <div>Chat with <span data-bind="text: conversation().to().username"></span></div>
<div id="chat" style="height: 5em;max-height: 200px;overflow:scroll" data-bind="foreach: conversation().messages()"> <div id="chat" style="height: 5em;max-height: 200px;overflow:scroll" data-bind="foreach: conversation().messages()">
<div data-bind="text: messageFormatted"></div> <div data-bind="text: messageFormatted"></div>
</div> </div>
<form class="form-horizontal" data-bind="submit: conversation().send"> <form class="form-horizontal" data-bind="submit: conversation().send">
<textarea data-bind="value: conversation().draft"></textarea> <textarea data-bind="value: conversation().draft"></textarea>
<button class="btn btn-primary" type="submit">Send</button> <button class="btn btn-primary" type="submit">Send</button>
</form> </form>
</div> </div>
</div> </div>
<div class="alert alert-warning"> <div class="alert alert-warning">
<h5>Notifications</h5> <h5>Notifications</h5>
<ul data-bind="foreach: notifications"> <ul data-bind="foreach: notifications">
<li data-bind="text: notification"></li> <li data-bind="text: notification"></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<!-- 3rd party --> <!-- 3rd party -->
<script src="resources/js/jquery/jquery.js"></script> <script src="resources/js/jquery/jquery.js"></script>
<script src="resources/js/bootstrap/js/bootstrap.js"></script> <script src="resources/js/bootstrap/js/bootstrap.js"></script>
<script src="resources/js/knockout/knockout.js"></script> <script src="resources/js/knockout/knockout.js"></script>
<script src="resources/js/sockjs/sockjs.js"></script> <script src="resources/js/sockjs/sockjs.js"></script>
<script src="resources/js/stomp/lib/stomp.js"></script> <script src="resources/js/stomp/lib/stomp.js"></script>
<!-- application --> <!-- application -->
<script src="resources/js/message.js"></script> <script src="resources/js/message.js"></script>
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
var socket = new SockJS('/messages'); var socket = new SockJS('/messages');
var stompClient = Stomp.over(socket); var stompClient = Stomp.over(socket);
var appModel = new ApplicationModel(stompClient); var appModel = new ApplicationModel(stompClient);
ko.applyBindings(appModel); ko.applyBindings(appModel);
appModel.connect(); appModel.connect();
})(); })();
</script> </script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,124 +1,124 @@
<!DOCTYPE html SYSTEM "http://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" <html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head> <head>
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">SecureMail</title> <title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">SecureMail</title>
<link rel="icon" type="image/x-icon" th:href="@{/resources/img/favicon.ico}" href="../static/img/favicon.ico"/> <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> <link th:href="@{/resources/css/bootstrap.css}" href="../static/css/bootstrap.css" rel="stylesheet"></link>
<style type="text/css"> <style type="text/css">
/* Sticky footer styles /* Sticky footer styles
-------------------------------------------------- */ -------------------------------------------------- */
html, html,
body { body {
height: 100%; height: 100%;
/* The html and body elements cannot have any padding or margin. */ /* The html and body elements cannot have any padding or margin. */
} }
/* Wrapper for page content to push down footer */ /* Wrapper for page content to push down footer */
#wrap { #wrap {
min-height: 100%; min-height: 100%;
height: auto !important; height: auto !important;
height: 100%; height: 100%;
/* Negative indent footer by it's height */ /* Negative indent footer by it's height */
margin: 0 auto -60px; margin: 0 auto -60px;
} }
/* Set the fixed height of the footer here */ /* Set the fixed height of the footer here */
#push, #push,
#footer { #footer {
height: 60px; height: 60px;
} }
#footer { #footer {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
/* Lastly, apply responsive CSS fixes as necessary */ /* Lastly, apply responsive CSS fixes as necessary */
@media (max-width: 767px) { @media (max-width: 767px) {
#footer { #footer {
margin-left: -20px; margin-left: -20px;
margin-right: -20px; margin-right: -20px;
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
} }
} }
/* Custom page CSS /* Custom page CSS
-------------------------------------------------- */ -------------------------------------------------- */
/* Not required for template or sticky footer method. */ /* Not required for template or sticky footer method. */
.container { .container {
width: auto; width: auto;
max-width: 680px; max-width: 680px;
} }
.container .credit { .container .credit {
margin: 20px 0; margin: 20px 0;
text-align: center; text-align: center;
} }
a { a {
color: green; color: green;
} }
.navbar-form { .navbar-form {
margin-left: 1em; margin-left: 1em;
} }
</style> </style>
<link th:href="@{resources/css/bootstrap-responsive.css}" href="/static/css/bootstrap-responsive.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 --> <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--> <![endif]-->
</head> </head>
<body> <body>
<div id="wrap"> <div id="wrap">
<div class="navbar navbar-inverse navbar-static-top"> <div class="navbar navbar-inverse navbar-static-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" th:href="@{/}"><img th:src="@{/resources/img/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" <div class="nav-collapse collapse"
th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}"> th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}">
<div th:if="${currentUser != null}"> <div th:if="${currentUser != null}">
<form class="navbar-form pull-right" th:action="@{/logout}" method="post"> <form class="navbar-form pull-right" th:action="@{/logout}" method="post">
<input type="submit" value="Log out" /> <input type="submit" value="Log out" />
</form> </form>
<p class="navbar-text pull-right" th:text="${currentUser.firstName}"> <p class="navbar-text pull-right" th:text="${currentUser.firstName}">
sample_user sample_user
</p> </p>
</div> </div>
<ul class="nav"> <ul class="nav">
<li><a th:href="@{/}">IM</a></li> <li><a th:href="@{/}">IM</a></li>
<li><a th:href="@{/h2/}">H2</a></li> <li><a th:href="@{/h2/}">H2</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Begin page content --> <!-- Begin page content -->
<div class="container"> <div class="container">
<div class="alert alert-success" <div class="alert alert-success"
th:if="${globalMessage}" th:if="${globalMessage}"
th:text="${globalMessage}"> th:text="${globalMessage}">
Some Success message Some Success message
</div> </div>
<div layout:fragment="content"> <div layout:fragment="content">
Fake content Fake content
</div> </div>
</div> </div>
<div id="push"><!-- --></div> <div id="push"><!-- --></div>
</div> </div>
<div id="footer"> <div id="footer">
<div class="container"> <div class="container">
<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> <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>
</div> </div>
</body> </body>
</html> </html>

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