Compare commits

..

96 Commits

Author SHA1 Message Date
Eleftheria Stein
ce714708cb Add End-of-Life Notice 2020-11-18 12:59:08 +01:00
Eleftheria Stein
748d14e04a Remove from CI 2020-11-18 12:57:23 +01:00
Vedran Pavic
b4cbb56ef2 Update Jenkinsfile to use explicit JAVA_HOME 2019-05-27 09:47:22 +02:00
Joe Grandja
b94d3a0dd0 Next development version 2019-04-02 16:59:33 -04:00
Joe Grandja
8abcd9cc65 Release 2.0.10.RELEASE 2019-04-02 16:36:30 -04:00
Joe Grandja
207b69527b Upgrade Spring Security to 5.0.12.RELEASE
Fixes gh-1351
2019-04-02 16:15:42 -04:00
Vedran Pavic
dd12125085 Upgrade Spring Data to Kay-SR14
Resolves: #1350
2019-04-02 07:31:39 +02:00
Spring Operator
3af1327474 URL Cleanup
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

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

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

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

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

These URLs were intentionally ignored.

* http://java.sun.com/xml/ns/javaee with 4 occurrences
* http://www.hazelcast.com/schema/config with 6 occurrences
* http://www.springframework.org/schema/beans with 18 occurrences
* http://www.springframework.org/schema/context with 14 occurrences
* http://www.springframework.org/schema/jdbc with 2 occurrences
* http://www.springframework.org/schema/p with 5 occurrences
* http://www.springframework.org/schema/security with 4 occurrences
* http://www.springframework.org/schema/util with 4 occurrences
* http://www.w3.org/2001/XMLSchema-instance with 14 occurrences
2019-04-01 10:42:51 -05:00
Spring Operator
e296702da1 URL Cleanup (#1392)
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

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

* [ ] http://www.ultraq.net.nz/thymeleaf/layout (302) with 12 occurrences migrated to:
  https://github.com/ultraq/thymeleaf-layout-dialect ([https](https://www.ultraq.net.nz/thymeleaf/layout) result ConnectTimeoutException).
* [ ] http://192.168.1.100:8080/ (AnnotatedConnectException) with 1 occurrences migrated to:
  https://192.168.1.100:8080/ ([https](https://192.168.1.100:8080/) result ConnectTimeoutException).

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

* [ ] http://docs.hazelcast.org/docs/ with 8 occurrences migrated to:
  https://docs.hazelcast.org/docs/ ([https](https://docs.hazelcast.org/docs/) result 200).
* [ ] http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/ with 1 occurrences migrated to:
  https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/ ([https](https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/) result 200).
* [ ] http://docs.spring.io/spring-session/docs/current/reference/html5/ with 1 occurrences migrated to:
  https://docs.spring.io/spring-session/docs/current/reference/html5/ ([https](https://docs.spring.io/spring-session/docs/current/reference/html5/) result 200).
* [ ] http://infinispan.org/docs/dev/user_guide/user_guide.html with 1 occurrences migrated to:
  https://infinispan.org/docs/dev/user_guide/user_guide.html ([https](https://infinispan.org/docs/dev/user_guide/user_guide.html) result 200).
* [ ] http://logback.qos.ch/manual/groovy.html with 1 occurrences migrated to:
  https://logback.qos.ch/manual/groovy.html ([https](https://logback.qos.ch/manual/groovy.html) result 200).
* [ ] http://projects.spring.io/spring-session/ with 1 occurrences migrated to:
  https://projects.spring.io/spring-session/ ([https](https://projects.spring.io/spring-session/) result 200).
* [ ] http://redis.io/commands/expire with 1 occurrences migrated to:
  https://redis.io/commands/expire ([https](https://redis.io/commands/expire) result 200).
* [ ] http://redis.io/commands/hmset with 1 occurrences migrated to:
  https://redis.io/commands/hmset ([https](https://redis.io/commands/hmset) result 200).
* [ ] http://redis.io/topics/data-types with 1 occurrences migrated to:
  https://redis.io/topics/data-types ([https](https://redis.io/topics/data-types) result 200).
* [ ] http://redis.io/topics/notifications with 2 occurrences migrated to:
  https://redis.io/topics/notifications ([https](https://redis.io/topics/notifications) result 200).
* [ ] http://stackoverflow.com with 1 occurrences migrated to:
  https://stackoverflow.com ([https](https://stackoverflow.com) result 200).
* [ ] http://stackoverflow.com/tags/spring-session with 1 occurrences migrated to:
  https://stackoverflow.com/tags/spring-session ([https](https://stackoverflow.com/tags/spring-session) result 200).
* [ ] http://www.thymeleaf.org with 12 occurrences migrated to:
  https://www.thymeleaf.org ([https](https://www.thymeleaf.org) result 200).
* [ ] http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd with 5 occurrences migrated to:
  https://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd ([https](https://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd) result 200).
* [ ] http://contributor-covenant.org with 1 occurrences migrated to:
  https://contributor-covenant.org ([https](https://contributor-covenant.org) result 301).
* [ ] http://contributor-covenant.org/version/1/3/0/ with 1 occurrences migrated to:
  https://contributor-covenant.org/version/1/3/0/ ([https](https://contributor-covenant.org/version/1/3/0/) result 301).
* [ ] http://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html with 2 occurrences migrated to:
  https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html ([https](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html) result 301).
* [ ] http://www.maxmind.com with 2 occurrences migrated to:
  https://www.maxmind.com ([https](https://www.maxmind.com) result 302).

# Ignored
These URLs were intentionally ignored.

* http://java.sun.com/jsp/jstl/core with 8 occurrences
* http://localhost with 15 occurrences
* http://localhost:8080/ with 31 occurrences
* http://localhost:8080/h2-console/ with 3 occurrences
* http://localhost:8080/logout with 1 occurrences
* http://localhost:8080/test/index with 2 occurrences
* http://localhost:xxxxx/hazelcast/rest/maps/spring:session:sessions/7e8383a4-082c-4ffe-a4bc-c40fd3363c5e with 1 occurrences
* http://www.w3.org/1999/xhtml with 5 occurrences
* http://www.webjars.org/tags with 8 occurrences
2019-04-01 10:22:01 -05:00
Vedran Pavic
c6572e9679 Upgrade Spring Framework to 5.0.13.RELEASE
Resolves: #1349
2019-03-31 12:48:07 +02:00
Vedran Pavic
83569af5a1 Upgrade Reactor to Bismuth-SR17
Resolves: #1352
2019-03-29 22:41:15 +01:00
Vedran Pavic
d240e5ead0 Polish 2019-03-29 22:41:15 +01:00
Vedran Pavic
1d5b4149ae Update to latest Checkstyle DTDs 2019-03-29 22:41:15 +01:00
Vedran Pavic
8e2b51ffc6 Fix Checkstyle config 2019-03-29 22:41:15 +01:00
Vedran Pavic
48a83870e7 Upgrade spring-build-conventions to 0.0.25.RELEASE 2019-03-29 22:41:13 +01:00
Vedran Pavic
6cec5dd548 Upgrade Gradle to 4.10.3 2019-03-29 22:21:23 +01:00
Rob Winch
55a70fa5be Update Checkstyle to https Apache 2 License
Issue gh-1367
2019-03-14 22:33:21 -05:00
Spring Operator
61bea92e83 URL Cleanup
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

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

* http://www.apache.org/licenses/ with 1 occurrences migrated to:
  https://www.apache.org/licenses/ ([https](https://www.apache.org/licenses/) result 200).
* http://www.apache.org/licenses/LICENSE-2.0 with 264 occurrences migrated to:
  https://www.apache.org/licenses/LICENSE-2.0 ([https](https://www.apache.org/licenses/LICENSE-2.0) result 200).
* http://www.apache.org/licenses/LICENSE-2.0.html with 1 occurrences migrated to:
  https://www.apache.org/licenses/LICENSE-2.0.html ([https](https://www.apache.org/licenses/LICENSE-2.0.html) result 200).
2019-03-14 20:38:58 -05:00
Vedran Pavic
01c49c145b Ignore failed rename operation for deleted session
In scenario with concurrent requests attempting to change session id, the "ERR no such key" error will occur for a thread that comes in second. This commit addresses the problem by ignoring the aforementioned error.

Resolves: #1327
2019-01-29 21:51:34 +01:00
Vedran Pavic
7c8fe93f2f Upgrade samples to Spring Boot 2.0.8.RELEASE
Resolves: #1315
2019-01-16 22:21:56 +01:00
Vedran Pavic
144f4be830 Next development version 2019-01-12 09:54:53 +01:00
Rob Winch
357b4c8394 Release 2.0.9.RELEASE
Fixes: gh-1290
2019-01-10 21:39:58 -06:00
Rob Winch
a28bbdc997 Update to Spring Security 5.0.11.RELEASE
Fixes: gh-1293
2019-01-10 21:39:11 -06:00
Vedran Pavic
d6dc0f5cdd Update integration tests 2019-01-10 16:37:30 +01:00
Vedran Pavic
de521cd07c Upgrade test dependencies 2019-01-10 16:33:00 +01:00
Vedran Pavic
4662d5c303 Upgrade Spring Data to Kay-SR13
Resolves: #1289
2019-01-10 13:36:11 +01:00
Vedran Pavic
53de486da3 Upgrade Spring Framework to 5.0.11.RELEASE
Resolves: #1288
2019-01-09 16:23:14 +01:00
Vedran Pavic
d11e6ddfe9 Upgrade Reactor to Bismuth-SR15
Resolves: #1297
2019-01-08 21:47:35 +01:00
Vedran Pavic
6ed798e09f Fix Spring Security integration docs sample
Resolves: #1305
2019-01-08 21:46:19 +01:00
Vedran Pavic
adcfa87454 Polish 2018-12-26 20:17:14 +01:00
Vedran Pavic
c66fcf3f8b Fix assertion in Hazelcast tests 2018-12-24 21:00:23 +01:00
Vedran Pavic
ae6bc3e550 Upgrade samples to Spring Boot 2.0.7.RELEASE
Resolves: #1295
2018-12-17 21:36:21 +01:00
Roman Beskrovnyi
44f63cd301 Fix SessionRepositoryFilter Javadoc 2018-12-14 09:43:24 -06:00
Jeff
0a3dbfa43e Fix RedisOperationsSessionRepository javadoc typos
Resolves: #1275
2018-12-04 20:07:48 +01:00
Vedran Pavic
ae77a9db6c Next development version 2018-11-28 21:40:43 +01:00
Vedran Pavic
fe106ea7bb Release 2.0.8.RELEASE 2018-11-28 21:37:38 +01:00
Vedran Pavic
44ba9a97b7 Upgrade test dependencies 2018-11-28 19:23:01 +01:00
Vedran Pavic
0bdb106c30 Upgrade Spring Security to 5.0.10.RELEASE
Resolves: #1263
2018-11-28 19:22:31 +01:00
Vedran Pavic
43014247eb Upgrade Spring Data to Kay-SR12
Resolves: #1247
2018-11-27 15:11:13 +01:00
Vedran Pavic
7e8917ac47 Upgrade Spring Framework to 5.0.11.RELEASE
Resolves: #1246
2018-11-27 12:34:04 +01:00
Vedran Pavic
ad90867590 Polish 2018-11-26 19:10:13 +01:00
Vedran Pavic
32c28013f3 Make SessionUpdateEntryProcessor public
Resolves: #1265
2018-11-26 19:09:03 +01:00
Vedran Pavic
c5b43f096c Update integration tests 2018-11-26 19:07:20 +01:00
Vedran Pavic
82759642c3 Upgrade test dependencies 2018-11-26 18:59:40 +01:00
Vedran Pavic
74c5260754 Upgrade Reactor to Bismuth-SR14
Resolves: #1264
2018-11-23 19:35:01 +01:00
Vedran Pavic
55b4f6f017 Update integration tests 2018-11-02 23:15:25 +01:00
Vedran Pavic
9099bd5d3a Upgrade test dependencies 2018-11-02 23:15:13 +01:00
Vedran Pavic
afa1f0890e Upgrade samples to Spring Boot 2.0.6.RELEASE
Resolves: #1245
2018-11-02 22:59:28 +01:00
Vedran Pavic
ed3f6abf5d Polish contribution
Resolves: #1244
2018-11-02 22:51:14 +01:00
Josh Cummings
6c322631d4 Commit Session on Include Dispatch
The servlet spec disallows any writing of headers after an include has been issued.

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

See: #1244
2018-11-02 22:50:28 +01:00
Vedran Pavic
9575be9b7d Ensure HttpServletRequest#getRequestedSessionId API is respected
HttpSessionIdResolver supports resolving multiple requested session ids associated with the request - as a consequence, we need to validate the existence of requested session before returning the id. However, if no presented session ids do validate the null is returned, which violates the HttpServletRequest#getRequestedSessionId API.

This commit ensures that if no presented session ids are valid, we respect the HttpServletRequest#getRequestedSessionId API by returning first requested session id.

Resolves: #1236
2018-10-26 20:12:39 +02:00
Rob Winch
eae239febf Next Development Version 2018-10-15 20:12:43 -05:00
Rob Winch
b86b34ca2e Release 2.0.7.RELEASE 2018-10-15 20:11:03 -05:00
Rob Winch
c0a2220d3b Update to Spring Security 5.0.9.RELEASE
Fixes: gh-1226
2018-10-15 20:09:51 -05:00
Vedran Pavic
c9d6ef7f01 Upgrade integration tests 2018-10-15 19:59:54 +02:00
Vedran Pavic
c2c1311830 Upgrade test dependencies 2018-10-15 19:59:35 +02:00
Vedran Pavic
8c97a73b36 Upgrade Reactor to Bismuth-SR12
Resolves: #1225
2018-10-15 19:50:20 +02:00
Vedran Pavic
0886e237b6 Upgrade Spring Data to Kay-SR11
Resolves: #1224
2018-10-15 19:48:24 +02:00
Vedran Pavic
c57a286e35 Upgrade Spring Framework to 5.0.10.RELEASE
Resolves: #1209
2018-10-15 19:45:44 +02:00
Vedran Pavic
4c5f22900d Polish 2018-09-26 14:47:53 +02:00
Vedran Pavic
8a8f379b37 Disable network join in Hazelcast samples 2018-09-26 14:47:52 +02:00
Vedran Pavic
dc4a0ce61b Configure default LobHandler to use temporary LOBs on Oracle
JdbcOperationsSessionRepository recently introduced validation when inserting new session attributes in order to prevent data integrity violations in highly concurrent environments. This is done by using INSERT INTO ... SELECT statement to verify existence of session record in parent table. Such arrangement causes problems with Oracle if inserted attribute is of size 4 kb or more.

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

Resolves: #1208
2018-09-25 19:30:09 +02:00
Vedran Pavic
5944648c25 Fix SpringSessionRememberMeServices documentation example
Resolves: #1210
2018-09-25 19:27:39 +02:00
Vedran Pavic
4502724e8c Upgrade samples to Spring Boot 2.0.5.RELEASE
Closes gh-1194
2018-09-13 21:43:47 +02:00
Vedran Pavic
3c8cce652e Upgrade Gradle to 4.10.1 2018-09-13 21:43:11 +02:00
Vedran Pavic
425df2261f Update Jenkinsfile to specify node label 2018-09-13 18:24:10 +02:00
Vedran Pavic
7c6b143964 Ensure RedisHttpSessionConfiguration handles events for configured database
At present, RedisHttpSessionConfiguration doesn't take into account database index when handlng events. In situations where multiple apps use Spring Session with same Redis instance, but different database, this results in invalid session events.

This commits improves event handling in RedisHttpSessionConfiguration to ensure currently used database is considered.

Closes gh-1193
2018-09-13 18:23:59 +02:00
Vedran Pavic
39c640f456 Next development version 2018-09-11 00:21:34 +02:00
Vedran Pavic
f190da3757 Release 2.0.6.RELEASE 2018-09-11 00:19:56 +02:00
Vedran Pavic
34060cf0f6 Upgrade Spring Security to 5.0.8.RELEASE
Closes gh-1178
2018-09-10 23:00:13 +02:00
Vedran Pavic
ff10709f18 Polish
See gh-1185
2018-09-10 17:16:31 +02:00
Vedran Pavic
6e471f6441 Upgrade test dependencies 2018-09-10 17:16:12 +02:00
Vedran Pavic
a03be43450 Upgrade Spring Data to Kay-SR10
Closes gh-1186
2018-09-10 16:38:21 +02:00
Vedran Pavic
7e8f500df0 Verify session existence before update in ReactiveRedisOperationsSessionRepository
Currently, ReactiveRedisOperationsSessionRepository#save does not ensure session's existence before executing update. This can result in an invalid session record in Redis, since write use only delta, and in turn to error while retrieving the invalid session record.

This commit adds check for session existence if session is being updated.

Closes gh-1185
2018-09-10 00:04:06 +02:00
Vedran Pavic
b27742ce3e Upgrade Spring Framework to 5.0.9.RELEASE
Closes gh-1152
2018-09-07 18:09:27 +02:00
Vedran Pavic
af44e71af0 Update integration tests 2018-09-06 18:05:32 +02:00
Vedran Pavic
055c2bcb93 Upgrade test dependencies 2018-09-06 17:57:19 +02:00
Vedran Pavic
3335deb5d5 Upgrade Reactor to Bismuth-SR11
Closes gh-1180
2018-09-06 07:40:45 +02:00
Vedran Pavic
1079e9e016 Ignore failed rename operation for deleted session
Attempting to change session id for a deleted session currently results in "ERR no such key" error on rename operation of expired key. This commit addressed the problem by ignoring the aforementioned error.

Closes #1177
2018-09-04 23:18:07 +02:00
Vedran Pavic
5fa52be8d1 Upgrade Gradle to 4.10 2018-09-04 23:17:46 +02:00
Vedran Pavic
c90952031f Fix Jenkinsfile 2018-08-27 10:16:17 +02:00
Vedran Pavic
ce308ca513 Fix Jenkinsfile 2018-08-27 08:43:01 +02:00
Vedran Pavic
c8f78e510e Upgrade spring-build-conventions to 0.0.18.RELEASE 2018-08-24 23:59:12 +02:00
Rob Winch
748ba70a01 Fix settings.gradle on Windows
Fixes: gh-1169
2018-08-22 20:36:08 +02:00
Vedran Pavic
dc1c7cdf02 Polish 2018-08-13 08:36:23 +02:00
Vedran Pavic
3a972bef76 Insert new attributes conditionally in JDBC repo
At present, the insert of new attributes in JdbcOperationsSessionRepository is done unconditionally. This can cause data integrity violation errors with concurrent requests, where one request attempts to add new session attribute while the other, concurrent request, deletes the session.

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

Closes gh-1151
2018-08-13 08:36:08 +02:00
Vedran Pavic
6b7fc3af08 Fix Jenkinsfile 2018-08-01 11:01:44 +02:00
Vedran Pavic
535160bc92 Update Jenkinsfile
- set check stage timeout to 30 minutes
- set build discared to keep last 10 builds
- handle deploy stage errors
- general formatting improvements
2018-08-01 10:54:45 +02:00
Vedran Pavic
606e08007e Upgrade samples to Spring Boot 2.0.4.RELEASE
Closes gh-1138
2018-07-31 17:10:12 +02:00
Vedran Pavic
06fa33e48b Next development version 2018-07-29 09:54:16 +02:00
Vedran Pavic
d7c2e8e79c Release 2.0.5.RELEASE 2018-07-29 09:48:45 +02:00
Vedran Pavic
6bec95a298 Polish 2018-07-27 13:27:27 +02:00
Vedran Pavic
9249a140c9 Upgrade dependencies 2018-07-27 11:05:33 +02:00
Vedran Pavic
7f6dc801e0 Upgrade Spring Data to Kay-SR9
Closes gh-1122
2018-07-27 10:23:12 +02:00
Vedran Pavic
83d46ad685 Upgrade Spring Security to 5.0.7.RELEASE
Closes gh-1123
2018-07-27 01:14:21 +02:00
Vedran Pavic
21cef2b7fa Upgrade Spring Framework to 5.0.8.RELEASE
Closes gh-1121
2018-07-26 23:25:13 +02:00
117 changed files with 1777 additions and 3112 deletions

149
Jenkinsfile vendored
View File

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

View File

@@ -1,3 +1,9 @@
[NOTE]
======
This branch of Spring Session has reached its https://github.com/spring-projects/spring-boot/wiki/Supported-Versions[End of Life], meaning that there are no further maintenance releases or security patches planned.
Please migrate to a supported branch as soon as possible.
======
= Spring Session
image:https://travis-ci.org/spring-projects/spring-session.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-session"] image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]

View File

@@ -1,38 +1,18 @@
buildscript {
ext {
releaseBuild = version.endsWith('RELEASE')
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.1.4.RELEASE'
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release/' }
}
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.25.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release' }
}
}
apply plugin: 'io.spring.convention.root'
group = 'org.springframework.session'
description = 'Spring Session'
gradle.taskGraph.whenReady { graph ->
def jacocoEnabled = graph.allTasks.any { it instanceof JacocoReport }
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = 1.8
}
plugins.withType(JacocoPlugin) {
tasks.withType(Test) {
jacoco.enabled = jacocoEnabled
}
}
}
}
ext.releaseBuild = version.endsWith('RELEASE')
ext.snapshotBuild = version.endsWith('SNAPSHOT')
ext.milestoneBuild = !(releaseBuild || snapshotBuild)

View File

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

View File

@@ -2,17 +2,15 @@
Rob Winch, Vedran Pavić
:toc:
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` when you use Spring Boot.
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` when using Spring Boot.
NOTE: You can find the completed guide in the <<httpsession-jdbc-boot-sample, httpsession-jdbc-boot sample application>>.
NOTE: The completed guide can be found in the <<httpsession-jdbc-boot-sample, httpsession-jdbc-boot sample application>>.
== Updating Dependencies
Before you use Spring Session, you must update your dependencies.
Before you use Spring Session, you must ensure to update your dependencies.
We assume you are working with a working Spring Boot web application.
If you use Maven, you must add the following dependencies:
If you are using Maven, ensure to add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -26,9 +24,8 @@ If you use Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
Spring Boot provides dependency management for Spring Session modules, so you need not explicitly declare the dependency version.
Spring Boot provides dependency management for Spring Session modules, so there's no need to explicitly declare dependency version.
// tag::config[]
@@ -36,106 +33,95 @@ Spring Boot provides dependency management for Spring Session modules, so you ne
== Spring Boot Configuration
After adding the required dependencies, we can create our Spring Boot configuration.
Thanks to first-class auto configuration support, setting up Spring Session backed by a relational database is as simple as adding a single configuration property to your `application.properties`.
The following listing shows how to do so:
Thanks to first-class auto configuration support, setting up Spring Session backed by a relational database is as simple as adding a single configuration property to your `application.properties`:
====
.src/main/resources/application.properties
----
spring.session.store-type=jdbc # Session store type.
----
====
Under the hood, Spring Boot applies configuration that is equivalent to manually adding the `@EnableJdbcHttpSession` annotation.
This creates a Spring bean with the name of `springSessionRepositoryFilter`. That bean implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
Under the hood, Spring Boot will apply configuration that is equivalent to manually adding `@EnableJdbcHttpSession` annotation.
This creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
You can further customize by using `application.properties`.
The following listing shows how to do so:
Further customization is possible using `application.properties`:
====
.src/main/resources/application.properties
----
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds are used.
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds will be used.
spring.session.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of the database table used to store sessions.
----
====
For more information, see the https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-session[Spring Session] portion of the Spring Boot documentation.
For more information, refer to https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-session[Spring Session] portion of the Spring Boot documentation.
[[httpsession-jdbc-boot-configuration]]
== Configuring the `DataSource`
== Configuring the DataSource
Spring Boot automatically creates a `DataSource` that connects Spring Session to an embedded instance of an H2 database.
In a production environment, you need to update your configuration to point to your relational database.
For example, you can include the following in your application.properties:
Spring Boot automatically creates a `DataSource` that connects Spring Session to an embedded instance of H2 database.
In a production environment you need to ensure to update your configuration to point to your relational database.
For example, you can include the following in your *application.properties*
====
.src/main/resources/application.properties
----
spring.datasource.url= # JDBC URL of the database.
spring.datasource.username= # Login username of the database.
spring.datasource.password= # Login password of the database.
----
====
For more information, see the https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-configure-datasource[Configure a DataSource] portion of the Spring Boot documentation.
For more information, refer to https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-configure-datasource[Configure a DataSource] portion of the Spring Boot documentation.
[[httpsession-jdbc-boot-servlet-configuration]]
== Servlet Container Initialization
Our <<httpsession-jdbc-boot-spring-configuration,Spring Boot Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
Our <<httpsession-jdbc-boot-spring-configuration,Spring Boot Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
Last, we need to ensure that our Servlet Container (that is, 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.
Fortunately, Spring Boot takes care of both of these steps for us.
// end::config[]
[[httpsession-jdbc-boot-sample]]
== `httpsession-jdbc-boot` Sample Application
== httpsession-jdbc-boot Sample Application
The httpsession-jdbc-boot Sample Application demonstrates how to use Spring Session to transparently leverage an H2 database to back a web application's `HttpSession` when you use Spring Boot.
The httpsession-jdbc-boot Sample Application demonstrates how to use Spring Session to transparently leverage H2 database to back a web application's `HttpSession` when using Spring Boot.
[[httpsession-jdbc-boot-running]]
=== Running the `httpsession-jdbc-boot` Sample Application
=== Running the httpsession-jdbc-boot Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
====
----
$ ./gradlew :spring-session-sample-boot-jdbc:bootRun
----
====
You should now be able to access the application at http://localhost:8080/
[[httpsession-jdbc-boot-explore]]
=== Exploring the Security Sample Application
=== Exploring the security Sample Application
You can now try using the application.
To do so, enter the following to log in:
Try using the application. Enter the following to log in:
* *Username* _user_
* *Password* _password_
* **Username** _user_
* **Password** _password_
Now click the *Login* button.
You should now see a message indicating that your are logged in with the user entered previously.
The user's information is stored in the H2 database rather than Tomcat's `HttpSession` implementation.
Now click the **Login** button.
You should now see a message indicating your are logged in with the user entered previously.
The user's information is stored in H2 database rather than Tomcat's `HttpSession` implementation.
[[httpsession-jdbc-boot-how]]
=== How Does It Work?
=== How does it work?
Instead of using Tomcat's `HttpSession`, we persist the values in the H2 database.
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
Spring Session replaces the `HttpSession` with an implementation that is backed by a relational database.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession`, it is then persisted into the H2 database.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into H2 database.
When a new `HttpSession` is created, Spring Session creates a cookie named `SESSION` in your browser. That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
You can remove the session by using the H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL).
If you like, you can easily remove the session using H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL)
Now you can visit the application at http://localhost:8080/ and see that we are no longer authenticated.
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

View File

@@ -2,17 +2,15 @@
Rob Winch, Vedran Pavić
:toc:
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use Spring Boot.
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Spring Boot.
NOTE: You can find the completed guide in the <<boot-sample, boot sample application>>.
NOTE: The completed guide can be found in the <<boot-sample, boot sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure your dependencies.
Before you use Spring Session, you must ensure to update your dependencies.
We assume you are working with a working Spring Boot web application.
If you are using Maven, you must add the following dependencies:
If you are using Maven, ensure to add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -26,131 +24,114 @@ If you are using Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
Spring Boot provides dependency management for Spring Session modules, so you need not explicitly declare dependency version.
Spring Boot provides dependency management for Spring Session modules, so there's no need to explicitly declare dependency version.
[[boot-spring-configuration]]
== Spring Boot Configuration
After adding the required dependencies, we can create our Spring Boot configuration.
Thanks to first-class auto configuration support, setting up Spring Session backed by Redis is as simple as adding a single configuration property to your `application.properties`, as the following listing shows:
Thanks to first-class auto configuration support, setting up Spring Session backed by Redis is as simple as adding a single configuration property to your `application.properties`:
====
.src/main/resources/application.properties
----
spring.session.store-type=redis # Session store type.
----
====
Under the hood, Spring Boot applies configuration that is equivalent to manually adding `@EnableRedisHttpSession` annotation.
This creates a Spring bean with the name of `springSessionRepositoryFilter` that implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
Under the hood, Spring Boot will apply configuration that is equivalent to manually adding `@EnableRedisHttpSession` annotation.
This creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
Further customization is possible by using `application.properties`, as the following listing shows:
Further customization is possible using `application.properties`:
====
.src/main/resources/application.properties
----
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds is used.
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds will be used.
spring.session.redis.flush-mode=on-save # Sessions flush mode.
spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.
----
====
For more information, see the https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-session[Spring Session] portion of the Spring Boot documentation.
For more information, refer to https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-session[Spring Session] portion of the Spring Boot documentation.
[[boot-redis-configuration]]
== Configuring the Redis Connection
Spring Boot automatically creates a `RedisConnectionFactory` that connects Spring Session to a Redis Server on localhost on port 6379 (default port).
In a production environment, you need to update your configuration to point to your Redis server.
For example, you can include the following in your application.properties:
In a production environment you need to ensure to update your configuration to point to your Redis server.
For example, you can include the following in your *application.properties*
====
.src/main/resources/application.properties
----
spring.redis.host=localhost # Redis server host.
spring.redis.password= # Login password of the redis server.
spring.redis.port=6379 # Redis server port.
----
====
For more information, see the https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
For more information, refer to https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
[[boot-servlet-configuration]]
== Servlet Container Initialization
Our <<boot-spring-configuration,Spring Boot Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
Our <<boot-spring-configuration,Spring Boot Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
Last, we need to ensure that our servlet container (that is, 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.
Fortunately, Spring Boot takes care of both of these steps for us.
[[boot-sample]]
== Boot Sample Application
The Boot Sample Application demonstrates how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use Spring Boot.
The Boot Sample Application demonstrates how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Spring Boot.
[[boot-running]]
=== Running the Boot Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :spring-session-sample-boot-redis:bootRun
----
====
NOTE: For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
You should now be able to access the application at http://localhost:8080/
[[boot-explore]]
=== Exploring the `security` Sample Application
=== Exploring the security Sample Application
Now you can try using the application. Enter the following to log in:
Try using the application. Enter the following to log in:
* *Username* _user_
* *Password* _password_
* **Username** _user_
* **Password** _password_
Now click the *Login* button.
Now click the **Login** button.
You should now see a message indicating your are logged in with the user entered previously.
The user's information is stored in Redis rather than Tomcat's `HttpSession` implementation.
[[boot-how]]
=== How Does It Work?
=== How does it work?
Instead of using Tomcat's `HttpSession`, we persist the values in Redis.
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
Spring Session replaces the `HttpSession` with an implementation that is backed by Redis.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession`, it is then persisted into Redis.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Redis.
When a new `HttpSession` is created, Spring Session creates a cookie named `SESSION` in your browser.
That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
You can remove the session by using redis-cli.
For example, on a Linux based system you can type the following:
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
====
----
$ redis-cli keys '*' | xargs redis-cli del
----
====
TIP: The Redis documentation has instructions for https://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key.
To do so, enter the following into your terminal, being sure 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
----
=====
Now you can 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

@@ -7,8 +7,9 @@ This guide describes how to use Spring Session to ensure that WebSocket messages
// tag::disclaimer[]
NOTE: Spring Session's WebSocket support works only with Spring's WebSocket support.
Specifically,it does not work with using https://www.jcp.org/en/jsr/detail?id=356[JSR-356] directly, because JSR-356 does not have a mechanism for intercepting incoming WebSocket messages.
NOTE: Spring Session's WebSocket support only works with Spring's WebSocket support.
Specifically it does not work with using https://www.jcp.org/en/jsr/detail?id=356[JSR-356] directly.
This is due to the fact that JSR-356 does not have a mechanism for intercepting incoming WebSocket messages.
// end::disclaimer[]
@@ -16,27 +17,24 @@ Specifically,it does not work with using https://www.jcp.org/en/jsr/detail?id=35
The first step is to integrate Spring Session with the HttpSession. These steps are already outlined in the link:httpsession.html[HttpSession Guide].
Please make sure you have already integrated Spring Session with HttpSession before proceeding.
Please make sure you have already integrated Spring Session with the HttpSession before proceeding.
// tag::config[]
[[websocket-spring-configuration]]
== Spring Configuration
In a typical Spring WebSocket application, you would implement `WebSocketMessageBrokerConfigurer`.
In a typical Spring WebSocket application users would implement `WebSocketMessageBrokerConfigurer`.
For example, the configuration might look something like the following:
====
[source,java]
----
include::{websocketdoc-test-dir}WebSocketConfig.java[tags=class]
----
====
We can update our configuration to use Spring Session's WebSocket support.
The following example shows how to do so:
We can easily update our configuration to use Spring Session's WebSocket support.
For example:
====
.src/main/java/samples/config/WebSocketConfig.java
[source,java]
----
@@ -45,9 +43,8 @@ include::{samples-dir}boot/websocket/src/main/java/sample/config/WebSocketConfig
To hook in the Spring Session support we only need to change two things:
<1> Instead of implementing `WebSocketMessageBrokerConfigurer`, we extend `AbstractSessionWebSocketMessageBrokerConfigurer`
<1> Instead of implementing `WebSocketMessageBrokerConfigurer` we extend `AbstractSessionWebSocketMessageBrokerConfigurer`
<2> We rename the `registerStompEndpoints` method to `configureStompEndpoints`
====
What does `AbstractSessionWebSocketMessageBrokerConfigurer` do behind the scenes?
@@ -55,89 +52,87 @@ What does `AbstractSessionWebSocketMessageBrokerConfigurer` do behind the scenes
This ensures a custom `SessionConnectEvent` is fired that contains the `WebSocketSession`.
The `WebSocketSession` is necessary to terminate any WebSocket connections that are still open when a Spring Session is terminated.
* `SessionRepositoryMessageInterceptor` is added as a `HandshakeInterceptor` to every `StompWebSocketEndpointRegistration`.
This ensures that the `Session` is added to the WebSocket properties to enable updating the last accessed time.
This ensures that the Session is added to the WebSocket properties to enable updating the last accessed time.
* `SessionRepositoryMessageInterceptor` is added as a `ChannelInterceptor` to our inbound `ChannelRegistration`.
This ensures that every time an inbound message is received, that the last accessed time of our Spring Session is updated.
* `WebSocketRegistryListener` is created as a Spring bean.
This ensures that we have a mapping of all of the `Session` IDs to the corresponding WebSocket connections.
* `WebSocketRegistryListener` is created as a Spring Bean.
This ensures that we have a mapping of all of the Session id to the corresponding WebSocket connections.
By maintaining this mapping, we can close all the WebSocket connections when a Spring Session (HttpSession) is terminated.
// end::config[]
[[websocket-sample]]
== `websocket` Sample Application
== websocket Sample Application
The `websocket` sample application demonstrates how to use Spring Session with WebSockets.
The websocket sample application demonstrates how to use Spring Session with WebSockets.
=== Running the `websocket` Sample Application
=== Running the websocket Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
====
----
$ ./gradlew :spring-session-sample-boot-websocket:bootRun
----
====
[TIP]
=====
For the purposes of testing session expiration, you may want to change the session expiration to be 1 minute (the default is 30 minutes) by adding the following configuration property before starting the application:
====
For the purposes of testing session expiration, you may want to change the session expiration to be 1 minute (default is 30 minutes) by adding the following configuration property starting before the application:
.src/main/resources/application.properties
----
server.servlet.session.timeout=1m # Session timeout. If a duration suffix is not specified, seconds will be used.
----
====
=====
NOTE: For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost.
See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :spring-session-sample-boot-websocket:bootRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the `websocket` Sample Application
=== Exploring the websocket Sample Application
Now you can try using the application. Authenticate with the following information:
Try using the application. Authenticate with the following information:
* *Username* _rob_
* *Password* _password_
* **Username** _rob_
* **Password** _password_
Now click the *Login* button. You should now be authenticated as the user **rob**.
Now click the **Login** button. You should now be authenticated as the user **rob**.
Open an incognito window and access http://localhost:8080/
You are prompted with a login form. Authenticate with the following information:
You will be prompted with a log in form. Authenticate with the following information:
* *Username* _luke_
* *Password* _password_
* **Username** _luke_
* **Password** _password_
Now send a message from rob to luke. The message should appear.
Now send a message from *rob* to *luke*. The message should appear.
Wait for two minutes and try sending a message from rob to luke again.
You can see that the message is no longer sent.
Wait for two minutes and try sending a message from *rob* to *luke* again.
You will see that the message is no longer sent.
[NOTE]
.Why two minutes?
====
Spring Session expires in 60 seconds, but the notification from Redis is not guaranteed to happen within 60 seconds.
Spring Session will expire in 60 seconds, but the notification from Redis is not guaranteed to happen within 60 seconds.
To ensure the socket is closed in a reasonable amount of time, Spring Session runs a background task every minute at 00 seconds that forcibly cleans up any expired sessions.
This means you need to wait at most two minutes before the WebSocket connection is terminated.
This means you will need to wait at most two minutes before the WebSocket connection is terminated.
====
You can now try accessing http://localhost:8080/
You are prompted to authenticate again.
Try accessing http://localhost:8080/
You will be prompted to authenticate again.
This demonstrates that the session properly expires.
Now repeat the same exercise, but instead of waiting two minutes, send a message from each of the users every 30 seconds.
You can see that the messages continue to be sent.
Now repeat the same exercise, but instead of waiting two minutes send a message from *each* of the users every 30 seconds.
You will see that the messages continue to be sent.
Try accessing http://localhost:8080/
You are not prompted to authenticate again.
You will not be prompted to authenticate again.
This demonstrates the session is kept alive.
NOTE: Only messages sent from a user keep the session alive.
This is because only messages coming from a user imply user activity.
Received messages do not imply activity and, thus, do not renew the session expiration.
Messages received do not imply activity and thus do not renew the session expiration.

View File

@@ -2,19 +2,17 @@
Eric Helgeson
:toc:
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use Grails 3.1
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Grails 3.1
NOTE: Grails 3.1 is based off spring boot 1.3, so much of the advanced configuration and options can be found in the Boot docs as well.
NOTE: Grails 3.1 is based off spring boot 1.3 so much of the advanced configuration and options can be found in the boot docs as well.
NOTE: You can find the completed guid in the <<grails3-sample, Grails 3 sample application>>.
NOTE: The completed guide can be found in the <<grails3-sample, Grails 3 sample application>>.
== Updating Dependencies
Before you use Spring Session, you must update your dependencies.
Before you use Spring Session, you must ensure to update your dependencies.
We assume you are working with a working Grails 3.1 web profile.
You must add the following dependencies:
Add the following dependencies:
====
.build.gradle
[source,groovy]
[subs="verbatim,attributes"]
@@ -24,13 +22,11 @@ dependencies {
compile 'org.springframework.session:spring-session:{spring-session-version}'
}
----
====
ifeval::["{version-snapshot}" == "true"]
Since we use a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
You must have the following in your build.gradle:
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
====
.build.gradle
[source,groovy]
----
@@ -40,14 +36,12 @@ repositories {
}
}
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we use a Milestone version, we need to add the Spring Milestone Maven Repository.
You must have the following in your build.gradle:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.build.gradle
[source,groovy]
----
@@ -57,7 +51,6 @@ repositories {
}
}
----
====
endif::[]
[[grails3-redis-configuration]]
@@ -65,9 +58,8 @@ endif::[]
Spring Boot automatically creates a `RedisConnectionFactory` that connects Spring Session to a Redis Server on localhost on port 6379 (default port).
In a production environment you need to ensure to update your configuration to point to your Redis server.
For example, you can include the following in your application.yml:
For example, you can include the following in your *application.yml*
====
.grails-app/conf/application.yml
[source,yml]
----
@@ -77,9 +69,8 @@ spring:
password: secret
port: 6397
----
====
For more information, see the https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
For more information, refer to https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
[[grails3-sample]]
== Grails 3 Sample Application
@@ -91,61 +82,52 @@ The Grails 3 Sample Application demonstrates how to use Spring Session to transp
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :spring-session-sample-misc-grails3:bootRun
----
NOTE:For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost.
See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
You should now be able to access the application at http://localhost:8080/test/index
[[grails3-explore]]
=== Exploring the `security` Sample Application
=== Exploring the security Sample Application
You can now try using the application. Enter the following to log in:
Try using the application. Enter the following to log in:
* *Username* _user_
* *Password* _password_
* **Username** _user_
* **Password** _password_
Now click the *Login* button.
You should now see a message indicating that your are logged in with the user entered previously.
Now click the **Login** button.
You should now see a message indicating your are logged in with the user entered previously.
The user's information is stored in Redis rather than Tomcat's `HttpSession` implementation.
[[grails3-how]]
=== How Does It Work?
=== How does it work?
Instead of using Tomcat's `HttpSession`, we persist the values in Redis.
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
Spring Session replaces the `HttpSession` with an implementation that is backed by Redis.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession`, it is then persisted into Redis.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Redis.
When a new `HttpSession` is created, Spring Session creates a cookie named `SESSION` in your browser.
That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
You can remove the session by using redis-cli.
For example, on a Linux based system you can type the following:
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
====
----
$ redis-cli keys '*' | xargs redis-cli del
----
====
TIP: The Redis documentation has instructions for https://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key.
To do so, enter the following into your terminal, being sure 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
----
====
Now you can visit the application at http://localhost:8080/test/index and see that we are no longer authenticated.
Now visit the application at http://localhost:8080/test/index and observe that we are no longer authenticated.
NOTE: Spring Session does not work with Grails flash scope without additional work.
See https://stackoverflow.com/a/43311427 for an explanation.
NOTE: Spring Session will not work with grails flash scope without additional work. +
See this answer for an explanation: https://stackoverflow.com/a/43311427

View File

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

View File

@@ -2,17 +2,15 @@
Tommy Ludwig; Rob Winch
:toc:
This guide describes how to use Spring Session along with Spring Security when you use Hazelcast as your data store.
It assumes that you have already applied Spring Security to your application.
This guide describes how to use Spring Session along with Spring Security using Hazelcast as your data store.
It assumes you have already applied Spring Security to your application.
NOTE: You cand find the completed guide in the <<hazelcast-spring-security-sample, Hazelcast Spring Security sample application>>.
NOTE: The completed guide can be found in the <<hazelcast-spring-security-sample, Hazelcast Spring Security sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
Before you use Spring Session, you must update your dependencies.
If you use Maven, you must add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -32,13 +30,11 @@ If you use Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
You must have the following in your pom.xml:
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -52,14 +48,12 @@ You must have the following in your pom.xml:
</repository>
</repositories>
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
You must have the following in your pom.xml:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -68,7 +62,6 @@ You must have the following in your pom.xml:
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
====
endif::[]
// tag::config[]
@@ -77,146 +70,126 @@ endif::[]
== Spring Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
To do so, add the following Spring Configuration:
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
Add the following Spring Configuration:
====
[source,java]
----
include::{docs-test-dir}docs/http/HazelcastHttpSessionConfig.java[tags=config]
----
<1> The `@EnableHazelcastHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance, Spring Session is backed by Hazelcast.
<2> In order to support retrieval of sessions by principal name index, an appropriate `ValueExtractor` needs to be registered.
<1> The `@EnableHazelcastHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by Hazelcast.
<2> In order to support retrieval of sessions by principal name index, appropriate `ValueExtractor` needs to be registered.
Spring Session provides `PrincipalNameExtractor` for this purpose.
<3> We create a `HazelcastInstance` that connects Spring Session to Hazelcast.
By default, the application starts and connects to an embedded instance of Hazelcast.
For more information on configuring Hazelcast, see the https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-configuration[reference documentation].
====
By default, an embedded instance of Hazelcast is started and connected to by the application.
For more information on configuring Hazelcast, refer to the https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-configuration[reference documentation].
== Servlet Container Initialization
Our <<security-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
Our <<security-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `SessionConfig` class.
Since our application is already loading Spring configuration by using our `SecurityInitializer` class, we can add our `SessionConfig` class to it.
The following listing shows how to do so:
Since our application is already loading Spring configuration using our `SecurityInitializer` class, we can simply add our `SessionConfig` class to it.
====
.src/main/java/sample/SecurityInitializer.java
[source,java]
----
include::{samples-dir}javaconfig/hazelcast/src/main/java/sample/SecurityInitializer.java[tags=class]
----
====
Last, we need to ensure that our Servlet Container (that is, 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.
It is extremely important that Spring Session's `springSessionRepositoryFilter` is invoked before Spring Security's `springSecurityFilterChain`.
Doing so ensures that the `HttpSession` that Spring Security uses is backed by Spring Session.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` that makes this doing so easy.
The following example shows how to do so:
This ensures that the `HttpSession` that Spring Security uses is backed by Spring Session.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` that makes this extremely easy.
You can find an example below:
====
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}javaconfig/hazelcast/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`.
By extending `AbstractHttpSessionApplicationInitializer`, we ensure that the Spring Bean named `springSessionRepositoryFilter` is registered with our servlet container for every request before Spring Security's `springSecurityFilterChain`.
By extending `AbstractHttpSessionApplicationInitializer` we ensure that the Spring Bean by the name `springSessionRepositoryFilter` is registered with our Servlet Container for every request before Spring Security's `springSecurityFilterChain`.
// end::config[]
[[hazelcast-spring-security-sample]]
== Hazelcast Spring Security Sample Application
This section describes how to work with the Hazelcast Spring Security sample application.
=== Running the Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
Hazelcast will run in embedded mode with your application by default, but if you want to connect
to a stand alone instance instead, you can configure it by following the instructions in the
https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-configuration[reference documentation].
====
----
$ ./gradlew :spring-session-sample-javaconfig-hazelcast:tomcatRun
----
====
NOTE: By default, Hazelcast runs in embedded mode with your application.
However, if you want to connect to a standalone instance instead, you can configure it by following the instructions in the https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-configuration[reference documentation].
You should now be able to access the application at http://localhost:8080/
=== Exploring the Security Sample Application
=== Exploring the security Sample Application
You can now try using the application.
To do so, enter the following to log in:
Try using the application. Enter the following to log in:
* *Username* _user_
* *Password* _password_
* **Username** _user_
* **Password** _password_
Now click the *Login* button.
You should now see a message indicating that your are logged in with the user entered previously.
Now click the **Login** button.
You should now see a message indicating your are logged in with the user entered previously.
The user's information is stored in Hazelcast rather than Tomcat's `HttpSession` implementation.
=== How Does It Work?
=== How does it work?
Instead of using Tomcat's `HttpSession`, we persist the values in Hazelcast.
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Hazelcast.
Spring Session replaces the `HttpSession` with an implementation that is backed by a `Map` in Hazelcast.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession`, it is then persisted into Hazelcast.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Hazelcast.
When a new `HttpSession` is created, Spring Session creates a cookie named `SESSION` in your browser. That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
=== Interacting with the Data Store
=== Interact with the data store
You can remove the session by using https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-java-client[a Java client],
If you like, you can remove the session using https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-java-client[a Java client],
https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#other-client-implementations[one of the other clients], or the
https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#management-center[management center].
==== Using the Console
==== Using the console
For example, to remove the session by using the management center console after connecting to your Hazelcast node, run the following commands:
For example, using the management center console after connecting to your Hazelcast node:
====
----
default> ns spring:session:sessions
spring:session:sessions> m.clear
----
====
TIP: The Hazelcast documentation has instructions for https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#executing-console-commands[the console].
Alternatively, you can also delete the explicit key. Enter the following into the console, being sure 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 the console ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
====
----
spring:session:sessions> m.remove 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
----
====
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
==== Using the REST API
As described in the section of the documentation that cover other clients, there is a
As described in the other clients section of the documentation, there is a
https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#rest-client[REST API]
provided by the Hazelcast node(s).
For example, you could delete an individual key as follows (being sure to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie):
For example, you could delete an individual key as follows (replacing `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie):
====
----
$ curl -v -X DELETE http://localhost:xxxxx/hazelcast/rest/maps/spring:session:sessions/7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
----
====
TIP: The port number of the Hazelcast node is printed to the console on startup. Replace `xxxxx` with the port number.
TIP: The port number of the Hazelcast node will be printed to the console on startup. Replace `xxxxx` above with the port number.
Now you can see that you are no longer authenticated with this session.
Now observe that you are no longer authenticated with this session.

View File

@@ -4,14 +4,12 @@ Rob Winch, Vedran Pavić
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` with Java Configuration.
NOTE: You can find the completed guide in the <<httpsession-jdbc-sample, httpsession-jdbc sample application>>.
NOTE: The completed guide can be found in the <<httpsession-jdbc-sample, httpsession-jdbc sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
Before you use Spring Session, you must update your dependencies.
If you use Maven, you must add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -32,13 +30,11 @@ If you use Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
You must have the following in your pom.xml:
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -52,14 +48,12 @@ You must have the following in your pom.xml:
</repository>
</repositories>
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
You must have the following in your pom.xml:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -68,7 +62,6 @@ You must have the following in your pom.xml:
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
====
endif::[]
// tag::config[]
@@ -78,95 +71,82 @@ endif::[]
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
To do so, add the following Spring Configuration:
Add the following Spring Configuration:
====
[source,java]
----
include::{samples-dir}javaconfig/jdbc/src/main/java/sample/Config.java[tags=class]
----
<1> The `@EnableJdbcHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter`.
That bean implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance, Spring Session is backed by a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of an H2 database.
We configure the H2 database to create database tables by using the SQL script that is included in Spring Session.
<1> The `@EnableJdbcHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of H2 database.
We configure the H2 database to create database tables using the SQL script which is included in Spring Session.
<3> We create a `transactionManager` that manages transactions for previously configured `dataSource`.
====
For additional information on how to configure data access related concerns, see the https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/data-access.html[Spring Framework Reference Documentation].
For additional information on how to configure data access related concerns, please refer to the https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/data-access.html[Spring Framework Reference Documentation].
== Java Servlet Container Initialization
Our <<httpsession-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
Our <<httpsession-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
Last, we need to ensure that our Servlet Container (that is, Tomcat) uses our `springSessionRepositoryFilter` for every request.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps easy.
The following example shows how to do so:
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` both of these steps extremely easy.
You can find an example below:
====
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}javaconfig/jdbc/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`.
<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`.
Doing so ensures that the Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container for every request.
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to ensure Spring loads our `Config`.
====
This ensures that the Spring Bean by the name `springSessionRepositoryFilter` is registered with our Servlet Container for every request.
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily ensure Spring loads our `Config`.
// end::config[]
[[httpsession-jdbc-sample]]
== `httpsession-jdbc` Sample Application
== httpsession-jdbc Sample Application
This section describes how to work with the `httpsession-jdbc` Sample Application.
=== Running the `httpsession-jdbc` Sample Application
=== Running the httpsession-jdbc Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
====
----
$ ./gradlew :spring-session-sample-javaconfig-jdbc:tomcatRun
----
====
You should now be able to access the application at http://localhost:8080/
=== Exploring the `httpsession-jdbc` Sample Application
=== Exploring the httpsession-jdbc Sample Application
Now you can try using the application. To do so, fill out the form with the following information:
Try using the application. Fill out the form with the following information:
* *Attribute Name:* _username_
* *Attribute Value:* _rob_
* **Attribute Name:** _username_
* **Attribute Value:** _rob_
Now click the *Set Attribute* button. You should now see the values displayed in the table.
Now click the **Set Attribute** button. You should now see the values displayed in the table.
=== How Does It Work?
=== How does it work?
We interact with the standard `HttpSession` in the `SessionServlet` shown in the following listing:
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
====
.src/main/java/sample/SessionServlet.java
[source,java]
----
include::{samples-dir}javaconfig/jdbc/src/main/java/sample/SessionServlet.java[tags=class]
----
====
Instead of using Tomcat's `HttpSession`, we persist the values in H2 database.
Spring Session creates a cookie named `SESSION` in your browser.
That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
If you like, you can remove the session by using the H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL).
If you like, you can easily remove the session using H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL)
Now you can visit the application at http://localhost:8080/ and see 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

@@ -1,17 +1,15 @@
= Spring Session - HttpSession (Quick Start)
Rob Winch
:toc:
:version-snapshot: true
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` with Java Configuration.
NOTE: You can find the completed guide in the <<httpsession-sample, httpsession sample application>>.
NOTE: The completed guide can be found in the <<httpsession-sample, httpsession sample application>>.
== Updating Dependencies
Before you use Spring Session, you must update your dependencies.
If you are using Maven, you must add the following dependencies:
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -37,13 +35,11 @@ If you are using Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
You must have the following in your pom.xml:
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -57,14 +53,12 @@ You must have the following in your pom.xml:
</repository>
</repositories>
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
You must have the following in your pom.xml:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -73,7 +67,6 @@ You must have the following in your pom.xml:
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
====
endif::[]
// tag::config[]
@@ -82,22 +75,20 @@ endif::[]
== Spring Java Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
To do so, add the following Spring Configuration:
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
Add the following Spring Configuration:
====
[source,java]
----
include::{samples-dir}javaconfig/redis/src/main/java/sample/Config.java[tags=class]
----
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance, Spring Session is backed by Redis.
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by Redis.
<2> We create a `RedisConnectionFactory` that connects Spring Session to the Redis Server.
We configure the connection to connect to localhost on the default port (6379).
For more information on configuring Spring Data Redis, see the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
====
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, refer to the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
== Java Servlet Container Initialization
@@ -105,24 +96,22 @@ Our <<httpsession-spring-configuration,Spring Configuration>> created a Spring B
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
Last, we need to ensure that our Servlet Container (that is, Tomcat) uses our `springSessionRepositoryFilter` for every request.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps easy.
The following shows an example:
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` both of these steps extremely easy.
You can find an example below:
====
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}javaconfig/redis/src/main/java/sample/Initializer.java[tags=class]
----
NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`.
Doing so ensures that the Spring Bean by the name of `springSessionRepositoryFilter` is registered with our Servlet Container for every request.
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to ensure Spring loads our `Config`.
====
This ensures that the Spring Bean by the name `springSessionRepositoryFilter` is registered with our Servlet Container for every request.
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily ensure Spring loads our `Config`.
// end::config[]
[[httpsession-sample]]
@@ -130,66 +119,54 @@ Doing so ensures that the Spring Bean by the name of `springSessionRepositoryFil
=== Running the `httpsession` Sample Application
=== Running the httpsession Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :spring-session-sample-javaconfig-redis:tomcatRun
----
====
NOTE: For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost.
See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
You should now be able to access the application at http://localhost:8080/
=== Exploring the `httpsession` Sample Application
=== Exploring the httpsession Sample Application
Now you can try to use the application. To do so, fill out the form with the following information:
Try using the application. Fill out the form with the following information:
* *Attribute Name:* _username_
* *Attribute Value:* _rob_
* **Attribute Name:** _username_
* **Attribute Value:** _rob_
Now click the *Set Attribute* button. You should now see the values displayed in the table.
Now click the **Set Attribute** button. You should now see the values displayed in the table.
=== How Does It Work?
=== How does it work?
We interact with the standard `HttpSession` in the `SessionServlet` shown in the following listing:
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
====
.src/main/java/sample/SessionServlet.java
[source,java]
----
include::{samples-dir}javaconfig/redis/src/main/java/sample/SessionServlet.java[tags=class]
----
====
Instead of using Tomcat's `HttpSession`, we persist the values in Redis.
Spring Session creates a cookie named `SESSION` in your browser.
That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
You can remove the session by using redis-cli.
For example, on a Linux based system you can type the following:
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
====
----
$ redis-cli keys '*' | xargs redis-cli del
----
====
TIP: The Redis documentation has instructions for https://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key. Enter the following into your terminal, being sure 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
----
====
Now you can visit the application at http://localhost:8080/ and see 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

@@ -2,16 +2,14 @@
Rob Winch
:toc:
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use REST endpoints.
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using REST endpoints.
NOTE: You can find the completed guide in the <<rest-sample, rest sample application>>.
NOTE: The completed guide can be found in the <<rest-sample, rest sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
Before you use Spring Session, you must update your dependencies.
If you use Maven, you must add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -37,13 +35,11 @@ If you use Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
You must have the following in your pom.xml:
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -57,14 +53,12 @@ You must have the following in your pom.xml:
</repository>
</repositories>
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
You msut have the following in your pom.xml:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -73,7 +67,6 @@ You msut have the following in your pom.xml:
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
====
endif::[]
// tag::config[]
@@ -82,103 +75,83 @@ endif::[]
== Spring Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
To do so, add the following Spring Configuration:
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
Add the following Spring Configuration:
====
[source,java]
----
include::{samples-dir}javaconfig/rest/src/main/java/sample/HttpSessionConfig.java[tags=class]
----
<1> The `@EnableRedisHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance, Spring Session is backed by Redis.
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements `Filter`.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by Redis.
<2> We create a `RedisConnectionFactory` that connects Spring Session to the Redis Server.
We configure the connection to connect to localhost on the default port (6379).
For more information on configuring Spring Data Redis, see the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, refer to the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
<3> We customize Spring Session's HttpSession integration to use HTTP headers to convey the current session information instead of cookies.
====
== Servlet Container Initialization
Our <<rest-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
We provide the configuration in our Spring `MvcInitializer`, as the following example shows:
In order for our `Filter` to do its magic, Spring needs to load our `Config` class. We provide the configuration in our Spring `MvcInitializer` as shown below:
====
.src/main/java/sample/mvc/MvcInitializer.java
[source,java,indent=0]
----
include::{samples-dir}javaconfig/rest/src/main/java/sample/mvc/MvcInitializer.java[tags=config]
----
====
Last, we need to ensure that our Servlet Container (that is, Tomcat) uses our `springSessionRepositoryFilter` for every request.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` that makes doing so easy. To do so, extend the class with the default constructor, as the following example shows:
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` that makes this extremely easy. Simply extend the class with the default constructor as shown below:
====
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}javaconfig/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`.
// end::config[]
[[rest-sample]]
== `rest` Sample Application
== rest Sample Application
This section describes how to use the `rest` sample application.
=== Running the `rest` Sample Application
=== Running the rest Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
NOTE: For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost.
See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :spring-session-sample-javaconfig-rest:tomcatRun
----
====
You should now be able to access the application at http://localhost:8080/
=== Exploring the `rest` Sample Application
=== Exploring the rest Sample Application
You can now try to use the application. To do so, 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/
----
====
Note that you 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-
* *Password* _password_
* **Username** *user*
* **Password** *password*
Then run the following command:
====
----
$ curl -v http://localhost:8080/ -u user:password
----
====
In the output, you should notice the following:
In the output you will notice the following:
====
----
HTTP/1.1 200 OK
...
@@ -186,62 +159,44 @@ X-Auth-Token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3
{"username":"user"}
----
====
Specifically, you should notice the following things about our response:
Specifically, we notice the following things about our response:
* The HTTP Status is now a 200.
* We have a header a the name of `X-Auth-Token` and that contains a new session ID.
* The current username is displayed.
* The HTTP Status is now a 200
* We have a header with the name of *X-Auth-Token* which contains a new session id
* The current username is displayed
We can now use the `X-Auth-Token` to make another request without providing the username and password again. For example, the following command outputs the username, 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 username just as before:
====
----
$ curl -v http://localhost:8080/ -H "X-Auth-Token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
----
====
The only difference is that the session ID is not provided in the response headers because we are reusing an existing session.
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, the `X-Auth-Token` is displayed in the response with an empty value. For example, the following command invalidates 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"
----
====
You can 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.
====
----
HTTP/1.1 204 No Content
...
X-Auth-Token:
----
====
=== How Does It Work?
=== How does it work?
Spring Security interacts with the standard `HttpSession` in `SecurityContextPersistenceFilter`.
Instead of using Tomcat's `HttpSession`, Spring Security is now persisting the values in Redis.
Spring Session creates a header named `X-Auth-Token` in your browser.
That header contains the ID of your session.
Spring Session creates a header named X-Auth-Token in your browser that contains the id of your session.
If you like, you can easily see that the session is created in Redis.
To do so, create a session by using the following command:
If you like, you can easily see that the session is created in Redis. First create a session using the following:
====
----
$ curl -v http://localhost:8080/ -u user:password
----
====
In the output, you should notice the following:
In the output you will notice the following:
===
----
HTTP/1.1 200 OK
...
@@ -249,32 +204,17 @@ X-Auth-Token: 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
{"username":"user"}
----
====
Now you can remove the session by 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
----
====
TIP: The Redis documentation has instructions for https://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key.
To do so, enter the following into your terminal, being sure 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
----
====
We can now use the `X-Auth-Token` to make another request with the session we deleted and observe we that are prompted for 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"
----
====

View File

@@ -5,13 +5,12 @@ Rob Winch
This guide describes how to use Spring Session along with Spring Security.
It assumes you have already applied Spring Security to your application.
NOTE: You can find the completed guide in the <<security-sample, security sample application>>.
NOTE: The completed guide can be found in the <<security-sample, security sample application>>.
== Updating Dependencies
Before you use Spring Session, you must update your dependencies.
If you use Maven, you must add the following dependencies:
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -37,13 +36,11 @@ If you use Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
You must have the following in your pom.xml:
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -57,14 +54,12 @@ You must have the following in your pom.xml:
</repository>
</repositories>
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
You must have the following in your pom.xml:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -73,121 +68,107 @@ You must have the following in your pom.xml:
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
====
endif::[]
[[security-spring-configuration]]
== Spring Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
To do so, add the following Spring Configuration:
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
Add the following Spring Configuration:
====
[source,java]
----
include::{samples-dir}javaconfig/security/src/main/java/sample/Config.java[tags=class]
----
<1> The `@EnableRedisHttpSession` annotation creates a Spring bean with the name of `springSessionRepositoryFilter` that implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by Redis.
<2> We create a `RedisConnectionFactory` that connects Spring Session to the Redis Server.
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, see the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
====
For more information on configuring Spring Data Redis, refer to the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
== Servlet Container Initialization
Our <<security-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
Our <<security-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
Since our application is already loading Spring configuration by using our `SecurityInitializer` class, we can add our configuration class to it.
The following example shows how to do so:
Since our application is already loading Spring configuration using our `SecurityInitializer` class, we can simply add our Config class to it.
====
.src/main/java/sample/SecurityInitializer.java
[source,java]
----
include::{samples-dir}javaconfig/security/src/main/java/sample/SecurityInitializer.java[tags=class]
----
====
Last, we need to ensure that our Servlet Container (that is, 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.
It is extremely important that Spring Session's `springSessionRepositoryFilter` is invoked before Spring Security's `springSecurityFilterChain`.
This ensures that the `HttpSession` that Spring Security uses is backed by Spring Session.
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` that makes doing so easy.
The following example shows how to do so:
Fortunately, Spring Session provides a utility class named `AbstractHttpSessionApplicationInitializer` that makes this extremely easy.
You can find an example below:
====
.src/main/java/sample/Initializer.java
[source,java]
----
include::{samples-dir}javaconfig/security/src/main/java/sample/Initializer.java[tags=class]
----
====
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
By extending `AbstractHttpSessionApplicationInitializer`, we ensure that the Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container for every request before Spring Security's `springSecurityFilterChain` .
By extending `AbstractHttpSessionApplicationInitializer` we ensure that the Spring Bean by the name `springSessionRepositoryFilter` is registered with our Servlet Container for every request before Spring Security's `springSecurityFilterChain` .
[[security-sample]]
== `security` Sample Application
== security Sample Application
This section describes how to work with the `security` sample application.
=== Running the `security` Sample Application
=== Running the security Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :spring-session-sample-javaconfig-security:tomcatRun
----
====
NOTE: For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost.
See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
You should now be able to access the application at http://localhost:8080/
=== Exploring the `security` Sample Application
=== Exploring the security Sample Application
Now you can use the application. Enter the following to log in:
Try using the application. Enter the following to log in:
* *Username* _user_
* *Password* _password_
* **Username** _user_
* **Password** _password_
Now click the *Login* button.
Now click the **Login** button.
You should now see a message indicating your are logged in with the user entered previously.
The user's information is stored in Redis rather than Tomcat's `HttpSession` implementation.
=== How Does It Work?
=== How does it work?
Instead of using Tomcat's `HttpSession`, we persist the values in Redis.
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
Spring Session replaces the `HttpSession` with an implementation that is backed by Redis.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession`, it is then persisted into Redis.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Redis.
When a new `HttpSession` is created, Spring Session creates a cookie named `SESSION` in your browser.
That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
You can remove the session using redis-cli. For example, on a Linux-based system you can type the following command:
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
====
----
$ redis-cli keys '*' | xargs redis-cli del
----
====
TIP: The Redis documentation has instructions for https://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key.
Enter the following command into your terminal, being sure 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
Now you can visit the application at http://localhost:8080/ and see that we are no longer authenticated.
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

View File

@@ -4,14 +4,12 @@ Rob Winch, Vedran Pavić
This guide describes how to use Spring Session to transparently leverage a relational to back a web application's `HttpSession` with XML based configuration.
NOTE: You can find the completed guide in the <<httpsession-jdbc-xml-sample, httpsession-jdbc-xml sample application>>.
NOTE: The completed guide can be found in the <<httpsession-jdbc-xml-sample, httpsession-jdbc-xml sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
Before you use Spring Session, you must update your dependencies.
If you are using Maven, you must add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -32,13 +30,11 @@ If you are using Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
You must have the following in your pom.xml:
Since we are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -52,14 +48,12 @@ You must have the following in your pom.xml:
</repository>
</repositories>
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
You must have the following in your pom.xml:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -69,7 +63,6 @@ You must have the following in your pom.xml:
</repository>
----
endif::[]
====
// tag::config[]
@@ -77,10 +70,9 @@ endif::[]
== Spring XML Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
The following listing shows how to add the following Spring Configuration:
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
Add the following Spring Configuration:
====
.src/main/webapp/WEB-INF/spring/session.xml
[source,xml,indent=0]
----
@@ -88,94 +80,83 @@ include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/spring/session.xml[tags=b
----
<1> We use the combination of `<context:annotation-config/>` and `JdbcHttpSessionConfiguration` because Spring Session does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
This creates a Spring bean with the name of `springSessionRepositoryFilter`.
That bean implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance, Spring Session is backed by a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of an H2 database.
We configure the H2 database to create database tables by using the SQL script that is included in Spring Session.
This creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of H2 database.
We configure the H2 database to create database tables using the SQL script which is included in Spring Session.
<3> We create a `transactionManager` that manages transactions for previously configured `dataSource`.
====
For additional information on how to configure data access-related concerns, see the https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/data-access.html[Spring Framework Reference Documentation].
For additional information on how to configure data access related concerns, please refer to the https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/data-access.html[Spring Framework Reference Documentation].
== XML Servlet Container Initialization
Our <<httpsession-xml-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
Our <<httpsession-xml-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration.
We do so with the following configuration:
We do this with the following configuration:
====
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/web.xml[tags=context-param]
include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/web.xml[tags=listeners]
----
====
The https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/core.html#context-create[`ContextLoaderListener`] reads the `contextConfigLocation` and picks up our session.xml configuration.
The https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/core.html#context-create[ContextLoaderListener] reads the contextConfigLocation and picks up our session.xml configuration.
Last, we need to ensure that our Servlet Container (that is, 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.
The following snippet performs this last step for us:
====
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
----
====
The https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[`DelegatingFilterProxy`] looks up a bean named `springSessionRepositoryFilter` and casts it to a `Filter`.
For every request on which `DelegatingFilterProxy` is invoked, the `springSessionRepositoryFilter` is invoked.
The https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] will look up a Bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`.
For every request that `DelegatingFilterProxy` is invoked, the `springSessionRepositoryFilter` will be invoked.
// end::config[]
[[httpsession-jdbc-xml-sample]]
== `httpsession-jdbc-xml` Sample Application
== httpsession-jdbc-xml Sample Application
This section describes how to work with the `httpsession-jdbc-xml` Sample Application.
=== Running the `httpsession-jdbc-xml` Sample Application
=== Running the httpsession-jdbc-xml Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
====
----
$ ./gradlew :spring-session-sample-xml-jdbc:tomcatRun
----
====
You should now be able to access the application at http://localhost:8080/
=== Exploring the `httpsession-jdbc-xml` Sample Application
=== Exploring the httpsession-jdbc-xml Sample Application
Now you can try using the application. To do so, fill out the form with the following information:
Try using the application. Fill out the form with the following information:
* *Attribute Name:* _username_
* *Attribute Value:* _rob_
* **Attribute Name:** _username_
* **Attribute Value:** _rob_
Now click the *Set Attribute* button. You should now see the values displayed in the table.
Now click the **Set Attribute** button. You should now see the values displayed in the table.
=== How Does It Work?
=== How does it work?
We interact with the standard `HttpSession` in the following `SessionServlet`:
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
====
.src/main/java/sample/SessionServlet.java
[source,java]
----
include::{samples-dir}xml/jdbc/src/main/java/sample/SessionServlet.java[tags=class]
----
====
Instead of using Tomcat's `HttpSession`, we persist the values in the H2 database.
Spring Session creates a cookie named `SESSION` in your browser. That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
You can remove the session by using H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL)
If you like, you can easily remove the session using H2 web console available at: http://localhost:8080/h2-console/ (use `jdbc:h2:mem:testdb` for JDBC URL)
Now you can 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

@@ -2,15 +2,14 @@
Rob Winch
:toc:
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` with XML-based configuration.
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` with XML based configuration.
NOTE: You can find the completed guide in the <<httpsession-xml-sample, httpsession-xml sample application>>.
NOTE: The completed guide can be found in the <<httpsession-xml-sample, httpsession-xml sample application>>.
== Updating Dependencies
Before you use Spring Session, you must update your dependencies.
If you use Maven, you must add the following dependencies:
Before you use Spring Session, you must ensure to update your dependencies.
If you are using Maven, ensure to add the following dependencies:
====
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
@@ -36,13 +35,11 @@ If you use Maven, you must add the following dependencies:
</dependency>
</dependencies>
----
====
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
You must have the following in your pom.xml:
Since we are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -56,14 +53,12 @@ You must have the following in your pom.xml:
</repository>
</repositories>
----
====
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
You must have the following in your pom.xml:
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
====
.pom.xml
[source,xml]
----
@@ -72,7 +67,6 @@ You must have the following in your pom.xml:
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
====
endif::[]
// tag::config[]
@@ -81,10 +75,9 @@ endif::[]
== Spring XML Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
To do so, add the following Spring Configuration:
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
Add the following Spring Configuration:
====
.src/main/webapp/WEB-INF/spring/session.xml
[source,xml,indent=0]
----
@@ -92,13 +85,12 @@ include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/spring/session.xml[tags=
----
<1> We use the combination of `<context:annotation-config/>` and `RedisHttpSessionConfiguration` because Spring Session does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
This creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements `Filter`.
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance, Spring Session is backed by Redis.
This creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by Redis.
<2> We create a `RedisConnectionFactory` that connects Spring Session to the Redis Server.
We configure the connection to connect to localhost on the default port (6379)
For more information on configuring Spring Data Redis, see the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
====
For more information on configuring Spring Data Redis, refer to the https://docs.spring.io/spring-data/data-redis/docs/{spring-data-redis-version}/reference/html/[reference documentation].
== XML Servlet Container Initialization
@@ -106,99 +98,83 @@ Our <<httpsession-xml-spring-configuration,Spring Configuration>> created a Spri
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration.
We can do so with the following configuration:
We do this with the following configuration:
====
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/web.xml[tags=context-param]
include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/web.xml[tags=listeners]
----
====
The https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/core.html#context-create[`ContextLoaderListener`] reads the contextConfigLocation and picks up our session.xml configuration.
The https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/core.html#context-create[ContextLoaderListener] reads the contextConfigLocation and picks up our session.xml configuration.
Last, we need to ensure that our Servlet Container (that is, 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.
The following snippet performs this last step for us:
====
.src/main/webapp/WEB-INF/web.xml
[source,xml,indent=0]
----
include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
----
====
The https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[`DelegatingFilterProxy`] looks up a Bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`.
For every request that `DelegatingFilterProxy` is invoked, the `springSessionRepositoryFilter` is invoked.
The https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] will look up a Bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`.
For every request that `DelegatingFilterProxy` is invoked, the `springSessionRepositoryFilter` will be invoked.
// end::config[]
[[httpsession-xml-sample]]
== `httpsession-xml` Sample Application
== httpsession-xml Sample Application
This section describes how to work with the `httpsession-xml` sample application.
=== Running the `httpsession-xml` Sample Application
=== Running the httpsession-xml Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
NOTE: For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
[NOTE]
====
For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :spring-session-sample-xml-redis:tomcatRun
----
====
You should now be able to access the application at http://localhost:8080/
=== Exploring the `httpsession-xml` Sample Application
=== Exploring the httpsession-xml Sample Application
Now you can try using the application. Fill out the form with the following information:
Try using the application. Fill out the form with the following information:
* *Attribute Name:* _username_
* *Attribute Value:* _rob_
* **Attribute Name:** _username_
* **Attribute Value:** _rob_
Now click the *Set Attribute* button. You should now see the values displayed in the table.
Now click the **Set Attribute** button. You should now see the values displayed in the table.
=== How Does It Work?
=== How does it work?
We interact with the standard `HttpSession` in the `SessionServlet` shown in the following listing:
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
====
.src/main/java/sample/SessionServlet.java
[source,java]
----
include::{samples-dir}xml/redis/src/main/java/sample/SessionServlet.java[tags=class]
----
====
Instead of using Tomcat's `HttpSession`, we persist the values in Redis.
Spring Session creates a cookie named SESSION in your browser.
That cookie contains the ID of your session.
You can view the cookies (with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies[Chrome] or https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector[Firefox]).
You can remove the session using redis-cli.
For example, on a Linux based system you can type the following:
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
====
----
$ redis-cli keys '*' | xargs redis-cli del
----
====
TIP: The Redis documentation has instructions for https://redis.io/topics/quickstart[installing redis-cli].
Alternatively, you can also delete the explicit key. To do so, enter the following into your terminal, being sure 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
----
====
Now you can visit the application at http://localhost:8080/ and see 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.

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -52,7 +52,9 @@ public class FindByIndexNameSessionRepositoryTests {
// tag::findby-username[]
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
.findByPrincipalName(username);
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username);
// end::findby-username[]
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -43,7 +43,6 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
@@ -87,6 +86,5 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -43,7 +43,6 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@@ -87,6 +86,5 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// end::class[]

View File

@@ -1 +1,2 @@
version=2.1.6.RELEASE
springBootVersion=2.0.8.RELEASE
version=2.0.11.BUILD-SNAPSHOT

View File

@@ -1,33 +1,32 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Californium-SR8'
mavenBom 'org.springframework:spring-framework-bom:5.1.7.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-SR8'
mavenBom 'org.springframework.security:spring-security-bom:5.1.5.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.11.2'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR1'
mavenBom 'org.springframework:spring-framework-bom:5.0.13.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR14'
mavenBom 'org.springframework.security:spring-security-bom:5.0.12.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.10.5'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.11.4') {
dependencySet(group: 'com.hazelcast', version: '3.9.4') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'com.h2database:h2:1.4.199'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8'
dependency 'com.zaxxer:HikariCP:3.3.1'
dependency 'com.h2database:h2:1.4.197'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.1.6.RELEASE'
dependency 'io.lettuce:lettuce-core:5.1.3.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'javax.servlet:javax.servlet-api:3.1.0'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:8.0.16'
dependency 'mysql:mysql-connector-java:8.0.13'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.12.2'
dependency 'org.assertj:assertj-core:3.11.1'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.1'
dependency 'org.mockito:mockito-core:2.27.0'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.3.0'
dependency 'org.mockito:mockito-core:2.23.4'
dependency 'org.postgresql:postgresql:42.2.5'
}
}

View File

@@ -46,7 +46,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class FindByUsernameTests {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private MockMvc mockMvc;

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -44,7 +44,10 @@ public class IndexController {
@RequestMapping("/")
public String index(Principal principal, Model model) {
Collection<? extends Session> usersSessions = this.sessions
.findByPrincipalName(principal.getName()).values();
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName())
.values();
model.addAttribute("sessions", usersSessions);
return "index";
}
@@ -53,8 +56,9 @@ public class IndexController {
@RequestMapping(value = "/sessions/{sessionIdToDelete}", method = RequestMethod.DELETE)
public String removeSession(Principal principal,
@PathVariable String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions
.findByPrincipalName(principal.getName()).keySet();
Set<String> usersSessionIds = this.sessions.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {
this.sessions.deleteById(sessionIdToDelete);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -34,7 +34,6 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
/**
* @author Eddú Meléndez
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(tagName = "button")
@FindBy(name = "submit")
private WebElement button;
public Form(SearchContext context) {

View File

@@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@AutoConfigureMockMvc
public class HttpRedisJsonTest {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private MockMvc mockMvc;

View File

@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class RedisSerializerTest {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@SpringSessionRedisOperations
private RedisTemplate<Object, Object> sessionRedisTemplate;

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -45,7 +45,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class BootTests {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private MockMvc mockMvc;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(tagName = "button")
@FindBy(name = "submit")
private WebElement button;
public Form(SearchContext context) {

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class AttributeTests {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@LocalServerPort
private int port;

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -23,6 +23,5 @@ dependencies {
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.springframework.security:spring-security-test"
integrationTestCompile "org.testcontainers:testcontainers"
testCompile "org.testcontainers:testcontainers"
}

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -42,7 +42,7 @@ import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Rob Winch
@@ -52,7 +52,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationTests {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Value("${local.server.port}")
private String port;
@@ -70,8 +70,8 @@ public class ApplicationTests {
ListenableFuture<WebSocketSession> wsSession = sockJsClient.doHandshake(
this.webSocketHandler, "ws://localhost:" + this.port + "/sockjs");
assertThatExceptionOfType(ExecutionException.class)
.isThrownBy(() -> wsSession.get().sendMessage(new TextMessage("a")));
assertThatThrownBy(() -> wsSession.get().sendMessage(new TextMessage("a")))
.isInstanceOf(ExecutionException.class);
}
@TestConfiguration

View File

@@ -5,7 +5,7 @@ dependencyManagement {
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.32.0'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.29.3'
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
dependency 'org.webjars:bootstrap:2.3.2'

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(tagName = "button")
@FindBy(name = "submit")
private WebElement button;
public Form(SearchContext context) {

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -17,6 +17,8 @@ dependencies {
testCompile "org.springframework.security:spring-security-test"
testCompile "org.assertj:assertj-core"
testCompile "org.springframework:spring-test"
integrationTestCompile "org.testcontainers:testcontainers"
}
gretty {

View File

@@ -54,7 +54,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@WebAppConfiguration
public class RestMockMvcTests {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;

View File

@@ -32,7 +32,7 @@ import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Pool Dolorier
@@ -57,9 +57,9 @@ public class RestTests {
public void unauthenticatedUserSentToLogInPage() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
assertThatExceptionOfType(HttpClientErrorException.class)
.isThrownBy(() -> getForUser(this.baseUrl + "/", headers, String.class))
.satisfies((e) -> assertThat(e.getStatusCode())
assertThatThrownBy(() -> getForUser(this.baseUrl + "/", headers, String.class))
.isInstanceOf(HttpClientErrorException.class)
.satisfies((e) -> assertThat(((HttpClientErrorException) e).getStatusCode())
.isEqualTo(HttpStatus.UNAUTHORIZED));
}

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -34,7 +34,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(tagName = "button")
@FindBy(css = "input[type='submit']")
private WebElement button;
public LoginPage(WebDriver driver) {
@@ -47,7 +47,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
}
public HomePage login(String user, String password) {

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -2,15 +2,12 @@ rootProject.name = 'spring-session-build'
FileTree buildFiles = fileTree(rootDir) {
include '**/*.gradle'
exclude 'build', '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*', 'out'
exclude '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*'
exclude '**/grails3'
gradle.startParameter.projectProperties.get('excludeProjects')?.split(',')?.each { excludeProject ->
exclude excludeProject
}
}
String rootDirPath = rootDir.absolutePath + File.separator
buildFiles.each { buildFile ->
buildFiles.each { File buildFile ->
if (buildFile.name == 'build.gradle') {
String buildFilePath = buildFile.parentFile.absolutePath
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')

View File

@@ -6,7 +6,6 @@ dependencies {
compile "org.springframework:spring-jcl"
optional "io.projectreactor:reactor-core"
optional "javax.annotation:javax.annotation-api"
optional "javax.servlet:javax.servlet-api"
optional "org.springframework:spring-context"
optional "org.springframework:spring-jdbc"

View File

@@ -19,22 +19,27 @@ package org.springframework.session;
import java.util.Map;
/**
* Extends a basic {@link SessionRepository} to allow finding sessions by the specified
* index name and index value.
* Extends a basic {@link SessionRepository} to allow finding a session id by the
* principal name. The principal name is defined by the {@link Session} attribute with the
* name {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}.
*
* @param <S> the type of Session being managed by this
* {@link FindByIndexNameSessionRepository}
* @author Rob Winch
* @author Vedran Pavic
*/
public interface FindByIndexNameSessionRepository<S extends Session>
extends SessionRepository<S> {
/**
* A session index that contains the current principal name (i.e. username).
* <p>
* It is the responsibility of the developer to ensure the index is populated since
* Spring Session is not aware of the authentication mechanism being used.
* A common session attribute that contains the current principal name (i.e.
* username).
* </p>
*
* <p>
* It is the responsibility of the developer to ensure the attribute is populated
* since Spring Session is not aware of the authentication mechanism being used.
* </p>
*
* @since 1.1
*/
@@ -42,34 +47,17 @@ public interface FindByIndexNameSessionRepository<S extends Session>
.concat(".PRINCIPAL_NAME_INDEX_NAME");
/**
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
* contain the specified index name index value.
* Find a Map of the session id to the {@link Session} of all sessions that contain
* the session attribute with the name
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the value of
* the specified principal name.
*
* @param indexName the name of the index (i.e.
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME})
* @param indexValue the value of the index to search for.
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
* of all sessions that contain the specified index name and index value. If no
* results are found, an empty {@code Map} is returned.
* @return a Map (never null) of the session id to the {@link Session} of all sessions
* that contain the session specified index name and the value of the specified index
* name. If no results are found, an empty Map is returned.
*/
Map<String, S> findByIndexNameAndIndexValue(String indexName, String indexValue);
/**
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
* contain the index with the name
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the
* specified principal name.
*
* @param principalName the principal name
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
* of all sessions that contain the specified principal name. If no results are found,
* an empty {@code Map} is returned.
* @since 2.1.0
*/
default Map<String, S> findByPrincipalName(String principalName) {
return findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
}
}

View File

@@ -53,7 +53,7 @@ public final class MapSession implements Session, Serializable {
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
private String id;
private final String originalId;
private String originalId;
private Map<String, Object> sessionAttrs = new HashMap<>();
private Instant creationTime = Instant.now();
private Instant lastAccessedTime = this.creationTime;
@@ -132,6 +132,10 @@ public final class MapSession implements Session, Serializable {
return this.originalId;
}
void setOriginalId(String originalId) {
this.originalId = originalId;
}
@Override
public String changeSessionId() {
String changedId = generateId();

View File

@@ -73,6 +73,7 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
public void save(MapSession session) {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
session.setOriginalId(session.getId());
}
this.sessions.put(session.getId(), new MapSession(session));
}

View File

@@ -76,6 +76,7 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
return Mono.fromRunnable(() -> {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
session.setOriginalId(session.getId());
}
this.sessions.put(session.getId(), new MapSession(session));
});

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -65,8 +65,9 @@ public class SpringSessionBackedSessionRegistry<S extends Session>
@Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository
.findByPrincipalName(name(principal)).values();
Collection<S> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
name(principal)).values();
List<SessionInformation> infos = new ArrayList<>();
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session

View File

@@ -16,13 +16,8 @@
package org.springframework.session.web.http;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.BitSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -46,22 +41,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
private static final Log logger = LogFactory.getLog(DefaultCookieSerializer.class);
private static final BitSet domainValid = new BitSet(128);
static {
for (char c = '0'; c <= '9'; c++) {
domainValid.set(c);
}
for (char c = 'a'; c <= 'z'; c++) {
domainValid.set(c);
}
for (char c = 'A'; c <= 'Z'; c++) {
domainValid.set(c);
}
domainValid.set('.');
domainValid.set('-');
}
private String cookieName = "SESSION";
private Boolean useSecureCookie;
@@ -82,8 +61,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
private String rememberMeRequestAttribute;
private String sameSite = "Lax";
/*
* (non-Javadoc)
*
@@ -98,8 +75,7 @@ public class DefaultCookieSerializer implements CookieSerializer {
for (Cookie cookie : cookies) {
if (this.cookieName.equals(cookie.getName())) {
String sessionId = (this.useBase64Encoding
? base64Decode(cookie.getValue())
: cookie.getValue());
? base64Decode(cookie.getValue()) : cookie.getValue());
if (sessionId == null) {
continue;
}
@@ -125,43 +101,38 @@ public class DefaultCookieSerializer implements CookieSerializer {
HttpServletRequest request = cookieValue.getRequest();
HttpServletResponse response = cookieValue.getResponse();
StringBuilder sb = new StringBuilder();
sb.append(this.cookieName).append('=');
String value = getValue(cookieValue);
if (value != null && value.length() > 0) {
validateValue(value);
sb.append(value);
}
int maxAge = getMaxAge(cookieValue);
if (maxAge > -1) {
sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge());
OffsetDateTime expires = (maxAge != 0)
? OffsetDateTime.now().plusSeconds(maxAge)
: Instant.EPOCH.atOffset(ZoneOffset.UTC);
sb.append("; Expires=")
.append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
}
String domain = getDomainName(request);
if (domain != null && domain.length() > 0) {
validateDomain(domain);
sb.append("; Domain=").append(domain);
}
String path = getCookiePath(request);
if (path != null && path.length() > 0) {
validatePath(path);
sb.append("; Path=").append(path);
}
if (isSecureCookie(request)) {
sb.append("; Secure");
}
if (this.useHttpOnlyCookie) {
sb.append("; HttpOnly");
}
if (this.sameSite != null) {
sb.append("; SameSite=").append(this.sameSite);
String requestedCookieValue = cookieValue.getCookieValue();
String actualCookieValue = (this.jvmRoute != null)
? requestedCookieValue + this.jvmRoute
: requestedCookieValue;
Cookie sessionCookie = new Cookie(this.cookieName, this.useBase64Encoding
? base64Encode(actualCookieValue) : actualCookieValue);
sessionCookie.setSecure(isSecureCookie(request));
sessionCookie.setPath(getCookiePath(request));
String domainName = getDomainName(request);
if (domainName != null) {
sessionCookie.setDomain(domainName);
}
response.addHeader("Set-Cookie", sb.toString());
if (this.useHttpOnlyCookie) {
sessionCookie.setHttpOnly(true);
}
if (cookieValue.getCookieMaxAge() < 0) {
if (this.rememberMeRequestAttribute != null
&& request.getAttribute(this.rememberMeRequestAttribute) != null) {
// the cookie is only written at time of session creation, so we rely on
// session expiration rather than cookie expiration if remember me is enabled
cookieValue.setCookieMaxAge(Integer.MAX_VALUE);
}
else if (this.cookieMaxAge != null) {
cookieValue.setCookieMaxAge(this.cookieMaxAge);
}
}
sessionCookie.setMaxAge(cookieValue.getCookieMaxAge());
response.addCookie(sessionCookie);
}
/**
@@ -192,81 +163,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
return new String(encodedCookieBytes);
}
private String getValue(CookieValue cookieValue) {
String requestedCookieValue = cookieValue.getCookieValue();
String actualCookieValue = requestedCookieValue;
if (this.jvmRoute != null) {
actualCookieValue = requestedCookieValue + this.jvmRoute;
}
if (this.useBase64Encoding) {
actualCookieValue = base64Encode(actualCookieValue);
}
return actualCookieValue;
}
private void validateValue(String value) {
int start = 0;
int end = value.length();
if ((end > 1) && (value.charAt(0) == '"') && (value.charAt(end - 1) == '"')) {
start = 1;
end--;
}
char[] chars = value.toCharArray();
for (int i = start; i < end; i++) {
char c = chars[i];
if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c
|| c == 0x7f) {
throw new IllegalArgumentException(
"Invalid character in cookie value: " + Integer.toString(c));
}
}
}
private int getMaxAge(CookieValue cookieValue) {
int maxAge = cookieValue.getCookieMaxAge();
if (maxAge < 0) {
if (this.rememberMeRequestAttribute != null && cookieValue.getRequest()
.getAttribute(this.rememberMeRequestAttribute) != null) {
// the cookie is only written at time of session creation, so we rely on
// session expiration rather than cookie expiration if remember me is
// enabled
cookieValue.setCookieMaxAge(Integer.MAX_VALUE);
}
else if (this.cookieMaxAge != null) {
cookieValue.setCookieMaxAge(this.cookieMaxAge);
}
}
return cookieValue.getCookieMaxAge();
}
private void validateDomain(String domain) {
int i = 0;
int cur = -1;
int prev;
char[] chars = domain.toCharArray();
while (i < chars.length) {
prev = cur;
cur = chars[i];
if (!domainValid.get(cur)
|| ((prev == '.' || prev == -1) && (cur == '.' || cur == '-'))
|| (prev == '-' && cur == '.')) {
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
}
i++;
}
if (cur == '.' || cur == '-') {
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
}
}
private void validatePath(String path) {
for (char ch : path.toCharArray()) {
if (ch < 0x20 || ch > 0x7E || ch == ';') {
throw new IllegalArgumentException("Invalid cookie path: " + path);
}
}
}
/**
* Sets if a Cookie marked as secure should be used. The default is to use the value
* of {@link HttpServletRequest#isSecure()}.
@@ -422,16 +318,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
this.rememberMeRequestAttribute = rememberMeRequestAttribute;
}
/**
* Set the value for the {@code SameSite} cookie directive. The default value is
* {@code Lax}.
* @param sameSite the SameSite directive value
* @since 2.1.0
*/
public void setSameSite(String sameSite) {
this.sameSite = sameSite;
}
private String getDomainName(HttpServletRequest request) {
if (this.domainName != null) {
return this.domainName;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -24,13 +24,8 @@ import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.session.Session;
/**
@@ -38,14 +33,11 @@ import org.springframework.session.Session;
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @author Vedran Pavic
* @since 1.1
*/
@SuppressWarnings("deprecation")
class HttpSessionAdapter<S extends Session> implements HttpSession {
private static final Log logger = LogFactory.getLog(HttpSessionAdapter.class);
private S session;
private final ServletContext servletContext;
@@ -137,28 +129,7 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
@Override
public void setAttribute(String name, Object value) {
checkState();
Object oldValue = this.session.getAttribute(name);
this.session.setAttribute(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue).valueUnbound(
new HttpSessionBindingEvent(this, name, oldValue));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
if (value instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) value)
.valueBound(new HttpSessionBindingEvent(this, name, value));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
}
}
@Override
@@ -169,17 +140,7 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
@Override
public void removeAttribute(String name) {
checkState();
Object oldValue = this.session.getAttribute(name);
this.session.removeAttribute(name);
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue)
.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
}
@Override

View File

@@ -87,6 +87,12 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
return Mono.just(session);
}
public Mono<Void> storeSession(WebSession session) {
@SuppressWarnings("unchecked")
SpringSessionWebSession springWebSession = (SpringSessionWebSession) session;
return this.sessions.save(springWebSession.session);
}
@Override
public Mono<WebSession> retrieveSession(String sessionId) {
return this.sessions.findById(sessionId)

View File

@@ -24,7 +24,7 @@ import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class MapSessionTests {
@@ -38,9 +38,9 @@ public class MapSessionTests {
@Test
public void constructorNullSession() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new MapSession((Session) null))
.withMessage("session cannot be null");
assertThatThrownBy(() -> new MapSession((Session) null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("session cannot be null");
}
@Test
@@ -70,9 +70,9 @@ public class MapSessionTests {
@Test
public void getRequiredAttributeWhenNullThenException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.session.getRequiredAttribute("attrName"))
.withMessage("Required attribute 'attrName' is missing.");
assertThatThrownBy(() -> this.session.getRequiredAttribute("attrName"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Required attribute 'attrName' is missing.");
}
@Test

View File

@@ -27,7 +27,7 @@ import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link ReactiveMapSessionRepository}.
@@ -60,9 +60,9 @@ public class ReactiveMapSessionRepositoryTests {
@Test
public void constructorMapWhenNullThenThrowsIllegalArgumentException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new ReactiveMapSessionRepository(null))
.withMessage("sessions cannot be null");
assertThatThrownBy(() -> new ReactiveMapSessionRepository(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("sessions cannot be null");
}
@Test

View File

@@ -38,7 +38,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link SpringHttpSessionConfiguration}.
@@ -63,9 +63,9 @@ public class SpringHttpSessionConfigurationTests {
@Test
public void noSessionRepositoryConfiguration() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> registerAndRefresh(EmptyConfiguration.class))
.withMessageContaining("org.springframework.session.SessionRepository");
assertThatThrownBy(() -> registerAndRefresh(EmptyConfiguration.class))
.isInstanceOf(UnsatisfiedDependencyException.class)
.hasMessageContaining("org.springframework.session.SessionRepository");
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.session.security;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -75,9 +74,7 @@ public class SpringSessionBackedSessionRegistryTest {
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isFalse();
}
@@ -93,9 +90,7 @@ public class SpringSessionBackedSessionRegistryTest {
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isTrue();
}
@@ -167,8 +162,9 @@ public class SpringSessionBackedSessionRegistryTest {
Map<String, Session> sessions = new LinkedHashMap<>();
sessions.put(session1.getId(), session1);
sessions.put(session2.getId(), session2);
when(this.sessionRepository.findByPrincipalName(USER_NAME))
.thenReturn(sessions);
when(this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, USER_NAME))
.thenReturn(sessions);
}
}

View File

@@ -27,7 +27,7 @@ import org.springframework.security.web.context.HttpSessionSecurityContextReposi
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -68,10 +68,9 @@ public class SpringSessionRememberMeServicesTests {
@Test
public void createWithNullParameter() {
this.rememberMeServices = new SpringSessionRememberMeServices();
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(
() -> this.rememberMeServices.setRememberMeParameterName(null))
.withMessage("rememberMeParameterName cannot be empty or null");
assertThatThrownBy(() -> this.rememberMeServices.setRememberMeParameterName(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("rememberMeParameterName cannot be empty or null");
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.

View File

@@ -26,14 +26,13 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.mock.web.MockCookie;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link DefaultCookieSerializer}.
@@ -214,9 +213,9 @@ public class DefaultCookieSerializerTests {
@Test
public void setDomainNameAndDomainNamePatternThrows() {
this.serializer.setDomainName("example.com");
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> this.serializer.setDomainNamePattern(".*"))
.withMessage("Cannot set both domainName and domainNamePattern");
assertThatThrownBy(() -> this.serializer.setDomainNamePattern(".*"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Cannot set both domainName and domainNamePattern");
}
// --- domainNamePattern ---
@@ -248,9 +247,9 @@ public class DefaultCookieSerializerTests {
@Test
public void setDomainNamePatternAndDomainNameThrows() {
this.serializer.setDomainNamePattern(".*");
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> this.serializer.setDomainName("example.com"))
.withMessage("Cannot set both domainName and domainNamePattern");
assertThatThrownBy(() -> this.serializer.setDomainName("example.com"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Cannot set both domainName and domainNamePattern");
}
// --- cookieName ---
@@ -274,9 +273,9 @@ public class DefaultCookieSerializerTests {
@Test
public void setCookieNameNullThrows() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.serializer.setCookieName(null))
.withMessage("cookieName cannot be null");
assertThatThrownBy(() -> this.serializer.setCookieName(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("cookieName cannot be null");
}
// --- cookiePath ---
@@ -467,39 +466,6 @@ public class DefaultCookieSerializerTests {
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
// --- sameSite ---
@Test
public void writeCookieDefaultSameSiteLax() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
}
@Test
public void writeCookieSetSameSiteLax() {
this.serializer.setSameSite("Lax");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
}
@Test
public void writeCookieSetSameSiteStrict() {
this.serializer.setSameSite("Strict");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Strict");
}
@Test
public void writeCookieSetSameSiteNull() {
this.serializer.setSameSite(null);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isNull();
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
this.serializer.setCookieName(cookieName);
@@ -512,8 +478,8 @@ public class DefaultCookieSerializerTests {
return new Cookie(name, value);
}
private MockCookie getCookie() {
return (MockCookie) this.response.getCookie(this.cookieName);
private Cookie getCookie() {
return this.response.getCookie(this.cookieName);
}
private String getCookieValue() {

View File

@@ -27,7 +27,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link HeaderHttpSessionIdResolver}.
@@ -74,9 +74,9 @@ public class HeaderHttpSessionIdResolverTests {
@Test
public void createResolverWithNullHeaderName() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new HeaderHttpSessionIdResolver(null))
.withMessage("headerName cannot be null");
assertThatThrownBy(() -> new HeaderHttpSessionIdResolver(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("headerName cannot be null");
}
@Test

View File

@@ -27,8 +27,6 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
@@ -38,8 +36,6 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import org.assertj.core.data.Offset;
@@ -62,7 +58,7 @@ import org.springframework.session.SessionRepository;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -1418,132 +1414,16 @@ public class SessionRepositoryFilterTests {
@Test
@SuppressWarnings("unused")
public void doesNotImplementOrdered() {
assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> {
assertThatThrownBy(() -> {
Ordered o = (Ordered) this.filter;
});
}).isInstanceOf(ClassCastException.class);
}
@Test
public void setHttpSessionIdResolverNull() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.filter.setHttpSessionIdResolver(null))
.withMessage("httpSessionIdResolver cannot be null");
}
@Test
public void bindingListenerBindListener() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindListenerThenUnbind() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
session.removeAttribute(bindingListenerName);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(0);
}
@Test
public void bindingListenerBindSameListenerTwice() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindListenerOverwrite() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener1 = new CountingHttpSessionBindingListener();
CountingHttpSessionBindingListener bindingListener2 = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener1);
session.setAttribute(bindingListenerName, bindingListener2);
}
});
assertThat(bindingListener1.getCounter()).isEqualTo(0);
assertThat(bindingListener2.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindThrowsException() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
bindingListener.setThrowException();
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(0);
}
@Test
public void bindingListenerBindListenerThenUnbindThrowsException() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
bindingListener.setThrowException();
session.removeAttribute(bindingListenerName);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
assertThatThrownBy(() -> this.filter.setHttpSessionIdResolver(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("httpSessionIdResolver cannot be null");
}
// --- helper methods
@@ -1648,39 +1528,4 @@ public class SessionRepositoryFilterTests {
}
private static class CountingHttpSessionBindingListener
implements HttpSessionBindingListener {
private final AtomicInteger counter = new AtomicInteger(0);
private final AtomicBoolean throwException = new AtomicBoolean(false);
@Override
public void valueBound(HttpSessionBindingEvent event) {
if (this.throwException.get()) {
this.throwException.compareAndSet(true, false);
throw new RuntimeException("bind exception");
}
this.counter.incrementAndGet();
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
if (this.throwException.get()) {
this.throwException.compareAndSet(true, false);
throw new RuntimeException("unbind exception");
}
this.counter.decrementAndGet();
}
int getCounter() {
return this.counter.get();
}
void setThrowException() {
this.throwException.compareAndSet(false, true);
}
}
}

View File

@@ -33,7 +33,7 @@ import org.springframework.session.Session;
import org.springframework.web.server.WebSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
@@ -69,9 +69,9 @@ public class SpringSessionWebSessionStoreTests<S extends Session> {
@Test
public void constructorWhenNullRepositoryThenThrowsIllegalArgumentException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new SpringSessionWebSessionStore<S>(null))
.withMessage("reactiveSessionRepository cannot be null");
assertThatThrownBy(() -> new SpringSessionWebSessionStore<S>(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("reactiveSessionRepository cannot be null");
}
@Test
@@ -253,6 +253,17 @@ public class SpringSessionWebSessionStoreTests<S extends Session> {
.containsExactly(new AbstractMap.SimpleEntry<>(attrName, attrValue));
}
@Test
public void storeSessionWhenInvokedThenSessionSaved() {
given(this.sessionRepository.save(this.createSession)).willReturn(Mono.empty());
WebSession createdSession = this.webSessionStore.createWebSession()
.block();
this.webSessionStore.storeSession(createdSession).block();
verify(this.sessionRepository).save(this.createSession);
}
@Test
public void retrieveSessionThenStarted() {
String id = "id";
@@ -275,9 +286,9 @@ public class SpringSessionWebSessionStoreTests<S extends Session> {
@Test
public void setClockWhenNullThenException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.webSessionStore.setClock(null))
.withMessage("clock cannot be null");
assertThatThrownBy(() -> this.webSessionStore.setClock(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("clock cannot be null");
}
@Test // gh-1114

View File

@@ -31,7 +31,7 @@ import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
@@ -56,9 +56,9 @@ public class WebSocketConnectHandlerDecoratorFactoryTests {
@Test
public void constructorNullEventPublisher() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new WebSocketConnectHandlerDecoratorFactory(null))
.withMessage("eventPublisher cannot be null");
assertThatThrownBy(() -> new WebSocketConnectHandlerDecoratorFactory(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("eventPublisher cannot be null");
}
@Test

View File

@@ -43,7 +43,7 @@ import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
@@ -82,9 +82,9 @@ public class SessionRepositoryMessageInterceptorTests {
@Test
public void preSendconstructorNullRepository() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new SessionRepositoryMessageInterceptor<>(null))
.withMessage("sessionRepository cannot be null");
assertThatThrownBy(() -> new SessionRepositoryMessageInterceptor<>(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("sessionRepository cannot be null");
}
@Test
@@ -134,16 +134,17 @@ public class SessionRepositoryMessageInterceptorTests {
@Test
public void setMatchingMessageTypesNull() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.interceptor.setMatchingMessageTypes(null))
.withMessage("matchingMessageTypes cannot be null or empty");
assertThatThrownBy(() -> this.interceptor.setMatchingMessageTypes(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("matchingMessageTypes cannot be null or empty");
}
@Test
public void setMatchingMessageTypesEmpty() {
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(
assertThatThrownBy(
() -> this.interceptor.setMatchingMessageTypes(Collections.emptySet()))
.withMessage("matchingMessageTypes cannot be null or empty");
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("matchingMessageTypes cannot be null or empty");
}
@Test

View File

@@ -29,7 +29,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
*/
public abstract class AbstractRedisITests {
private static final String DOCKER_IMAGE = "redis:5.0.4";
private static final String DOCKER_IMAGE = "redis:4.0.12";
protected static class BaseConfig {

View File

@@ -30,7 +30,6 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Integration tests for {@link ReactiveRedisOperationsSessionRepository}.
@@ -210,10 +209,7 @@ public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedi
this.repository.save(session).block();
toSave.setLastAccessedTime(Instant.now());
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> this.repository.save(toSave).block())
.withMessage("Session was invalidated");
this.repository.save(toSave).block();
assertThat(this.repository.findById(sessionId).block()).isNull();
assertThat(this.repository.findById(session.getId()).block()).isNotNull();

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -118,15 +118,6 @@ public class ReactiveRedisOperationsSessionRepository implements
this.redisFlushMode = redisFlushMode;
}
/**
* Returns the {@link ReactiveRedisOperations} used for sessions.
* @return the {@link ReactiveRedisOperations} used for sessions
* @since 2.1.0
*/
public ReactiveRedisOperations<String, Object> getSessionRedisOperations() {
return this.sessionRedisOperations;
}
@Override
public Mono<RedisSession> createSession() {
return Mono.defer(() -> {
@@ -156,9 +147,7 @@ public class ReactiveRedisOperationsSessionRepository implements
session.hasChangedSessionId() ? session.originalSessionId
: session.getId());
return this.sessionRedisOperations.hasKey(sessionKey)
.flatMap((exists) -> exists ? result
: Mono.error(new IllegalStateException(
"Session was invalidated")));
.flatMap((exists) -> exists ? result : Mono.empty());
}
}

View File

@@ -418,7 +418,7 @@ public class RedisOperationsSessionRepository implements
@Override
public void save(RedisSession session) {
session.save();
session.saveDelta();
if (session.isNew()) {
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
@@ -516,13 +516,12 @@ public class RedisOperationsSessionRepository implements
@Override
public RedisSession createSession() {
Duration maxInactiveInterval = Duration
.ofSeconds((this.defaultMaxInactiveInterval != null)
? this.defaultMaxInactiveInterval
: MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
RedisSession session = new RedisSession(maxInactiveInterval);
session.flushImmediateIfNecessary();
return session;
RedisSession redisSession = new RedisSession();
if (this.defaultMaxInactiveInterval != null) {
redisSession.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return redisSession;
}
@Override
@@ -712,17 +711,14 @@ public class RedisOperationsSessionRepository implements
/**
* Creates a new instance ensuring to mark all of the new attributes to be
* persisted in the next save operation.
*
* @param maxInactiveInterval the amount of time that the {@link Session} should
* be kept alive between client requests.
*/
RedisSession(Duration maxInactiveInterval) {
RedisSession() {
this(new MapSession());
this.cached.setMaxInactiveInterval(maxInactiveInterval);
this.delta.put(CREATION_TIME_ATTR, getCreationTime().toEpochMilli());
this.delta.put(MAX_INACTIVE_ATTR, (int) getMaxInactiveInterval().getSeconds());
this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime().toEpochMilli());
this.isNew = true;
this.flushImmediateIfNecessary();
}
/**
@@ -812,7 +808,7 @@ public class RedisOperationsSessionRepository implements
private void flushImmediateIfNecessary() {
if (RedisOperationsSessionRepository.this.redisFlushMode == RedisFlushMode.IMMEDIATE) {
save();
saveDelta();
}
}
@@ -821,20 +817,16 @@ public class RedisOperationsSessionRepository implements
this.flushImmediateIfNecessary();
}
private void save() {
saveChangeSessionId();
saveDelta();
}
/**
* Saves any attributes that have been changed and updates the expiration of this
* session.
*/
private void saveDelta() {
String sessionId = getId();
saveChangeSessionId(sessionId);
if (this.delta.isEmpty()) {
return;
}
String sessionId = getId();
getSessionBoundHashOperations(sessionId).putAll(this.delta);
String principalSessionKey = getSessionAttrNameKey(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
@@ -867,32 +859,30 @@ public class RedisOperationsSessionRepository implements
.onExpirationUpdated(originalExpiration, this);
}
private void saveChangeSessionId() {
String sessionId = getId();
if (sessionId.equals(this.originalSessionId)) {
return;
private void saveChangeSessionId(String sessionId) {
if (!sessionId.equals(this.originalSessionId)) {
if (!isNew()) {
String originalSessionIdKey = getSessionKey(this.originalSessionId);
String sessionIdKey = getSessionKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalSessionIdKey, sessionIdKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
String originalExpiredKey = getExpiredKey(this.originalSessionId);
String expiredKey = getExpiredKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalExpiredKey, expiredKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
}
this.originalSessionId = sessionId;
}
if (!isNew()) {
String originalSessionIdKey = getSessionKey(this.originalSessionId);
String sessionIdKey = getSessionKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalSessionIdKey, sessionIdKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
String originalExpiredKey = getExpiredKey(this.originalSessionId);
String expiredKey = getExpiredKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalExpiredKey, expiredKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
}
this.originalSessionId = sessionId;
}
private void handleErrNoSuchKeyError(NonTransientDataAccessException ex) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -23,14 +23,14 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
/**
* Annotation used to inject the Redis accessor used by Spring Session's Redis session
* repository.
* Annotation used to inject the {@link RedisOperations} instance used by Spring Session's
* {@link RedisOperationsSessionRepository}.
*
* @author Vedran Pavic
* @see org.springframework.session.data.redis.RedisOperationsSessionRepository#getSessionRedisOperations()
* @see org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository#getSessionRedisOperations()
* @since 2.0.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,

View File

@@ -21,7 +21,6 @@ import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -65,8 +64,6 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private ReactiveRedisConnectionFactory redisConnectionFactory;
private RedisSerializer<Object> defaultRedisSerializer;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@@ -110,13 +107,6 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
this.redisConnectionFactory = redisConnectionFactoryToUse;
}
@Autowired(required = false)
@Qualifier("springSessionDefaultRedisSerializer")
public void setDefaultRedisSerializer(
RedisSerializer<Object> defaultRedisSerializer) {
this.defaultRedisSerializer = defaultRedisSerializer;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
@@ -144,11 +134,10 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private ReactiveRedisTemplate<String, Object> createReactiveRedisTemplate() {
RedisSerializer<String> keySerializer = new StringRedisSerializer();
RedisSerializer<Object> defaultSerializer = (this.defaultRedisSerializer != null)
? this.defaultRedisSerializer
: new JdkSerializationRedisSerializer(this.classLoader);
RedisSerializer<Object> valueSerializer = new JdkSerializationRedisSerializer(
this.classLoader);
RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
.<String, Object>newSerializationContext(defaultSerializer)
.<String, Object>newSerializationContext(valueSerializer)
.key(keySerializer).hashKey(keySerializer).build();
return new ReactiveRedisTemplate<>(this.redisConnectionFactory,
serializationContext);

View File

@@ -36,7 +36,7 @@ import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepo
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
@@ -80,9 +80,9 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void constructorWithNullReactiveRedisOperations() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new ReactiveRedisOperationsSessionRepository(null))
.withMessageContaining("sessionRedisOperations cannot be null");
assertThatThrownBy(() -> new ReactiveRedisOperationsSessionRepository(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("sessionRedisOperations cannot be null");
}
@Test
@@ -95,16 +95,16 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void nullRedisKeyNamespace() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.repository.setRedisKeyNamespace(null))
.withMessage("namespace cannot be null or empty");
assertThatThrownBy(() -> this.repository.setRedisKeyNamespace(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("namespace cannot be null or empty");
}
@Test
public void emptyRedisKeyNamespace() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.repository.setRedisKeyNamespace(""))
.withMessage("namespace cannot be null or empty");
assertThatThrownBy(() -> this.repository.setRedisKeyNamespace(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("namespace cannot be null or empty");
}
@Test
@@ -125,9 +125,9 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void nullRedisFlushMode() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.repository.setRedisFlushMode(null))
.withMessage("redisFlushMode cannot be null");
assertThatThrownBy(() -> this.repository.setRedisFlushMode(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("redisFlushMode cannot be null");
}
@Test
@@ -346,16 +346,12 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getCreationTime()
.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getMaxInactiveInterval())
assertThat(session.getCreationTime()).isEqualTo(expected.getCreationTime());
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expected.getMaxInactiveInterval());
assertThat(
session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getLastAccessedTime()
.truncatedTo(ChronoUnit.MILLIS));
}).verifyComplete();
assertThat(session.getLastAccessedTime())
.isEqualTo(expected.getLastAccessedTime());
}).verifyComplete();
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2018 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.
@@ -59,7 +59,7 @@ import org.springframework.session.data.redis.RedisOperationsSessionRepository.R
import org.springframework.session.events.AbstractSessionEvent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -115,9 +115,9 @@ public class RedisOperationsSessionRepositoryTests {
@Test
public void setApplicationEventPublisherNull() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.redisRepository.setApplicationEventPublisher(null))
.withMessage("applicationEventPublisher cannot be null");
assertThatThrownBy(() -> this.redisRepository.setApplicationEventPublisher(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("applicationEventPublisher cannot be null");
}
@Test
@@ -343,8 +343,7 @@ public class RedisOperationsSessionRepositoryTests {
@Test
public void redisSessionGetAttributes() {
String attrName = "attrName";
RedisSession session = this.redisRepository.new RedisSession(
Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
RedisSession session = this.redisRepository.new RedisSession();
assertThat(session.getAttributeNames()).isEmpty();
session.setAttribute(attrName, "attrValue");
assertThat(session.getAttributeNames()).containsOnly(attrName);
@@ -433,12 +432,12 @@ public class RedisOperationsSessionRepositoryTests {
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getCreationTime().truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getCreationTime()).isEqualTo(expected.getCreationTime());
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expected.getMaxInactiveInterval());
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getLastAccessedTime())
.isEqualTo(expected.getLastAccessedTime());
}
@Test
@@ -499,11 +498,9 @@ public class RedisOperationsSessionRepositoryTests {
RedisSession session = sessionIdToSessions.get(sessionId);
assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(sessionId);
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(lastAccessed.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessed);
assertThat(session.getMaxInactiveInterval()).isEqualTo(maxInactive);
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(createdTime.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getCreationTime()).isEqualTo(createdTime);
}
@Test
@@ -758,23 +755,6 @@ public class RedisOperationsSessionRepositoryTests {
.isEqualTo(session.getCreationTime().toEpochMilli());
}
@Test // gh-1409
public void flushModeImmediateCreateWithCustomMaxInactiveInterval() {
given(this.redisOperations.boundHashOps(anyString()))
.willReturn(this.boundHashOperations);
given(this.redisOperations.boundSetOps(anyString()))
.willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString()))
.willReturn(this.boundValueOperations);
this.redisRepository.setDefaultMaxInactiveInterval(60);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.createSession();
Map<String, Object> delta = getDelta();
assertThat(delta.size()).isEqualTo(3);
assertThat(delta.get(RedisOperationsSessionRepository.MAX_INACTIVE_ATTR))
.isEqualTo(60);
}
@Test
public void flushModeImmediateSetAttribute() {
given(this.redisOperations.boundHashOps(anyString()))
@@ -859,9 +839,9 @@ public class RedisOperationsSessionRepositoryTests {
@Test
public void setRedisFlushModeNull() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.redisRepository.setRedisFlushMode(null))
.withMessage("redisFlushMode cannot be null");
assertThatThrownBy(() -> this.redisRepository.setRedisFlushMode(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("redisFlushMode cannot be null");
}
@Test
@@ -886,16 +866,16 @@ public class RedisOperationsSessionRepositoryTests {
@Test
public void setRedisKeyNamespaceNullNamespace() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.redisRepository.setRedisKeyNamespace(null))
.withMessage("namespace cannot be null or empty");
assertThatThrownBy(() -> this.redisRepository.setRedisKeyNamespace(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("namespace cannot be null or empty");
}
@Test
public void setRedisKeyNamespaceEmptyNamespace() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.redisRepository.setRedisKeyNamespace(" "))
.withMessage("namespace cannot be null or empty");
assertThatThrownBy(() -> this.redisRepository.setRedisKeyNamespace(" "))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("namespace cannot be null or empty");
}
@Test // gh-1120

View File

@@ -37,7 +37,7 @@ import org.springframework.session.data.redis.config.annotation.SpringSessionRed
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -184,10 +184,10 @@ public class RedisHttpSessionConfigurationTests {
@Test
public void multipleConnectionFactoryRedisConfig() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> registerAndRefresh(RedisConfig.class,
MultipleConnectionFactoryRedisConfig.class))
.withMessageContaining("expected single matching bean but found 2");
assertThatThrownBy(() -> registerAndRefresh(RedisConfig.class,
MultipleConnectionFactoryRedisConfig.class))
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("expected single matching bean but found 2");
}
private void registerAndRefresh(Class<?>... annotatedClasses) {

View File

@@ -27,16 +27,13 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
/**
@@ -73,22 +70,6 @@ public class RedisWebSessionConfigurationTests {
assertThat(repository).isNotNull();
}
@Test
public void springSessionRedisOperationsResolvingConfiguration() {
registerAndRefresh(RedisConfig.class,
SpringSessionRedisOperationsResolvingConfig.class);
ReactiveRedisOperationsSessionRepository repository = this.context
.getBean(ReactiveRedisOperationsSessionRepository.class);
assertThat(repository).isNotNull();
ReactiveRedisOperations<String, Object> springSessionRedisOperations = this.context
.getBean(SpringSessionRedisOperationsResolvingConfig.class)
.getSpringSessionRedisOperations();
assertThat(springSessionRedisOperations).isNotNull();
assertThat((ReactiveRedisOperations) ReflectionTestUtils.getField(repository,
"sessionRedisOperations")).isEqualTo(springSessionRedisOperations);
}
@Test
public void customNamespace() {
registerAndRefresh(RedisConfig.class, CustomNamespaceConfig.class);
@@ -194,40 +175,10 @@ public class RedisWebSessionConfigurationTests {
@Test
public void multipleConnectionFactoryRedisConfig() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> registerAndRefresh(RedisConfig.class,
MultipleConnectionFactoryRedisConfig.class))
.withMessageContaining("expected single matching bean but found 2");
}
@Test
@SuppressWarnings("unchecked")
public void customRedisSerializerConfig() {
registerAndRefresh(RedisConfig.class, CustomRedisSerializerConfig.class);
ReactiveRedisOperationsSessionRepository repository = this.context
.getBean(ReactiveRedisOperationsSessionRepository.class);
RedisSerializer<Object> redisSerializer = this.context
.getBean("springSessionDefaultRedisSerializer", RedisSerializer.class);
assertThat(repository).isNotNull();
assertThat(redisSerializer).isNotNull();
ReactiveRedisOperations redisOperations = (ReactiveRedisOperations) ReflectionTestUtils
.getField(repository, "sessionRedisOperations");
assertThat(redisOperations).isNotNull();
RedisSerializationContext serializationContext = redisOperations
.getSerializationContext();
assertThat(ReflectionTestUtils.getField(
serializationContext.getValueSerializationPair().getReader(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getValueSerializationPair().getWriter(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getHashValueSerializationPair().getReader(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getHashValueSerializationPair().getWriter(),
"serializer")).isEqualTo(redisSerializer);
assertThatThrownBy(() -> registerAndRefresh(RedisConfig.class,
MultipleConnectionFactoryRedisConfig.class))
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("expected single matching bean but found 2");
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
@@ -250,18 +201,6 @@ public class RedisWebSessionConfigurationTests {
}
@EnableRedisWebSession
static class SpringSessionRedisOperationsResolvingConfig {
@SpringSessionRedisOperations
private ReactiveRedisOperations<String, Object> springSessionRedisOperations;
public ReactiveRedisOperations<String, Object> getSpringSessionRedisOperations() {
return this.springSessionRedisOperations;
}
}
@EnableRedisWebSession(redisNamespace = REDIS_NAMESPACE)
static class CustomNamespaceConfig {
@@ -336,15 +275,4 @@ public class RedisWebSessionConfigurationTests {
}
@EnableRedisWebSession
static class CustomRedisSerializerConfig {
@Bean
@SuppressWarnings("unchecked")
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return mock(RedisSerializer.class);
}
}
}

View File

@@ -3,7 +3,6 @@ apply plugin: 'io.spring.convention.spring-module'
dependencies {
compile project(':spring-session-core')
compile "com.hazelcast:hazelcast"
compile "javax.annotation:javax.annotation-api"
compile "org.springframework:spring-context"
testCompile "javax.servlet:javax.servlet-api"

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2018 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.
@@ -48,7 +48,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
public class HazelcastClientRepositoryITests extends AbstractHazelcastRepositoryITests {
private static GenericContainer container = new GenericContainer<>(
"hazelcast/hazelcast:3.11.4")
"hazelcast/hazelcast:3.9.4")
.withExposedPorts(5701)
.withEnv("JAVA_OPTS",
"-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2018 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.
@@ -122,7 +122,7 @@ public class EnableHazelcastHttpSessionEventsTests<S extends Session> {
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToSave.getId()))
.isInstanceOf(SessionExpiredEvent.class);
assertThat(this.repository.findById(sessionToSave.getId())).isNull();
assertThat(this.repository.<Session>findById(sessionToSave.getId())).isNull();
}
@Test
@@ -147,18 +147,24 @@ public class EnableHazelcastHttpSessionEventsTests<S extends Session> {
@Test
public void saveUpdatesTimeToLiveTest() throws InterruptedException {
Object lock = new Object();
S sessionToSave = this.repository.createSession();
sessionToSave.setMaxInactiveInterval(Duration.ofSeconds(3));
this.repository.save(sessionToSave);
Thread.sleep(2000);
synchronized (lock) {
lock.wait(sessionToSave.getMaxInactiveInterval().minusMillis(500).toMillis());
}
// Get and save the session like SessionRepositoryFilter would.
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
sessionToUpdate.setLastAccessedTime(Instant.now());
this.repository.save(sessionToUpdate);
Thread.sleep(2000);
synchronized (lock) {
lock.wait(sessionToUpdate.getMaxInactiveInterval().minusMillis(100).toMillis());
}
assertThat(this.repository.findById(sessionToUpdate.getId())).isNotNull();
}
@@ -181,28 +187,6 @@ public class EnableHazelcastHttpSessionEventsTests<S extends Session> {
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isFalse();
}
@Test // gh-1300
public void updateMaxInactiveIntervalTest() throws InterruptedException {
S sessionToSave = this.repository.createSession();
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
this.repository.save(sessionToSave);
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
.isInstanceOf(SessionCreatedEvent.class);
this.registry.clear();
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
sessionToUpdate.setLastAccessedTime(Instant.now());
sessionToUpdate.setMaxInactiveInterval(Duration.ofSeconds(1));
this.repository.save(sessionToUpdate);
assertThat(this.registry.receivedEvent(sessionToUpdate.getId())).isTrue();
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToUpdate.getId()))
.isInstanceOf(SessionExpiredEvent.class);
assertThat(this.repository.findById(sessionToUpdate.getId())).isNull();
}
@Configuration
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
static class HazelcastSessionConfig {
@@ -216,7 +200,6 @@ public class EnableHazelcastHttpSessionEventsTests<S extends Session> {
public SessionEventRegistry sessionEventRegistry() {
return new SessionEventRegistry();
}
}
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.11.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
<user-code-deployment enabled="true">
<class-cache-mode>ETERNAL</class-cache-mode>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.11.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
<group>
<name>spring-session-it-test-idle-time-map-name</name>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.11.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
<group>
<name>spring-session-it-test-map-name</name>

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2018 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.
@@ -48,7 +48,6 @@ import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A {@link org.springframework.session.SessionRepository} implementation that stores
@@ -121,9 +120,6 @@ public class HazelcastSessionRepository implements
*/
public static final String PRINCIPAL_NAME_ATTRIBUTE = "principalName";
private static final boolean SUPPORTS_SET_TTL = ClassUtils
.hasAtLeastOneMethodWithName(IMap.class, "setTtl");
private static final Log logger = LogFactory.getLog(HazelcastSessionRepository.class);
private final HazelcastInstance hazelcastInstance;
@@ -242,9 +238,6 @@ public class HazelcastSessionRepository implements
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
}
if (session.maxInactiveIntervalChanged) {
if (SUPPORTS_SET_TTL) {
updateTtl(session);
}
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
}
if (!session.delta.isEmpty()) {
@@ -255,11 +248,6 @@ public class HazelcastSessionRepository implements
session.clearChangeFlags();
}
private void updateTtl(HazelcastSession session) {
this.sessions.setTtl(session.getId(),
session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
}
@Override
public HazelcastSession findById(String id) {
MapSession saved = this.sessions.get(id);

View File

@@ -20,7 +20,6 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import com.hazelcast.core.Offloadable;
import com.hazelcast.map.AbstractEntryProcessor;
import com.hazelcast.map.EntryProcessor;
@@ -34,7 +33,7 @@ import org.springframework.session.MapSession;
* @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession)
*/
public class SessionUpdateEntryProcessor
extends AbstractEntryProcessor<String, MapSession> implements Offloadable {
extends AbstractEntryProcessor<String, MapSession> {
private Instant lastAccessedTime;
@@ -68,11 +67,6 @@ public class SessionUpdateEntryProcessor
return Boolean.TRUE;
}
@Override
public String getExecutorName() {
return OFFLOADABLE_EXECUTOR;
}
void setLastAccessedTime(Instant lastAccessedTime) {
this.lastAccessedTime = lastAccessedTime;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2018 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.
@@ -39,10 +39,9 @@ import org.springframework.session.MapSession;
import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
@@ -79,9 +78,9 @@ public class HazelcastSessionRepositoryTests {
@Test
public void constructorNullHazelcastInstance() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new HazelcastSessionRepository(null))
.withMessage("HazelcastInstance must not be null");
assertThatThrownBy(() -> new HazelcastSessionRepository(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("HazelcastInstance must not be null");
}
@Test
@@ -260,12 +259,10 @@ public class HazelcastSessionRepositoryTests {
this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE);
HazelcastSession session = this.repository.createSession();
String sessionId = session.getId();
session.setMaxInactiveInterval(Duration.ofSeconds(1));
verify(this.sessions, times(1)).set(eq(sessionId),
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions).setTtl(eq(sessionId), anyLong(), any());
verify(this.sessions, times(1)).executeOnKey(eq(sessionId),
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()),
any(EntryProcessor.class));
this.repository.save(session);

View File

@@ -32,7 +32,7 @@ import org.springframework.session.hazelcast.config.annotation.SpringSessionHaze
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -62,10 +62,10 @@ public class HazelcastHttpSessionConfigurationTests {
@Test
public void noHazelcastInstanceConfiguration() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(
() -> registerAndRefresh(NoHazelcastInstanceConfiguration.class))
.withMessageContaining("HazelcastInstance");
assertThatThrownBy(
() -> registerAndRefresh(NoHazelcastInstanceConfiguration.class))
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("HazelcastInstance");
}
@Test
@@ -206,9 +206,10 @@ public class HazelcastHttpSessionConfigurationTests {
@Test
public void multipleHazelcastInstanceConfiguration() {
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
assertThatThrownBy(
() -> registerAndRefresh(MultipleHazelcastInstanceConfiguration.class))
.withMessageContaining("expected single matching bean but found 2");
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("expected single matching bean but found 2");
}
private void registerAndRefresh(Class<?>... annotatedClasses) {

View File

@@ -13,7 +13,6 @@ dependencies {
integrationTestCompile "com.h2database:h2"
integrationTestCompile "com.microsoft.sqlserver:mssql-jdbc"
integrationTestCompile "com.zaxxer:HikariCP"
integrationTestCompile "mysql:mysql-connector-java"
integrationTestCompile "org.apache.derby:derby"
integrationTestCompile "org.hsqldb:hsqldb"
@@ -22,6 +21,5 @@ dependencies {
integrationTestCompile "org.testcontainers:mariadb"
integrationTestCompile "org.testcontainers:mssqlserver"
integrationTestCompile "org.testcontainers:mysql"
integrationTestCompile "org.testcontainers:oracle-xe"
integrationTestCompile "org.testcontainers:postgresql"
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
/**
* Abstract base class for Testcontainers based {@link JdbcOperationsSessionRepository}
* integration tests.
*
* @author Vedran Pavic
*/
public abstract class AbstractContainerJdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
static class BaseContainerConfig extends BaseConfig {
@Bean
public HikariDataSource dataSource(JdbcDatabaseContainer databaseContainer) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(databaseContainer.getJdbcUrl());
dataSource.setUsername(databaseContainer.getUsername());
dataSource.setPassword(databaseContainer.getPassword());
return dataSource;
}
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource,
DatabasePopulator databasePopulator) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
}
}

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