Compare commits

...

1084 Commits

Author SHA1 Message Date
Vedran Pavic
546febd959 Release 2.2.0.M2 2019-06-14 23:26:47 +02:00
Vedran Pavic
ea857025bc Upgrade Spring Security to 5.2.0.M3
Resolves: #1437
2019-06-14 23:04:55 +02:00
Vedran Pavic
782167b306 Add SimpleRedisOperationsSessionRepository sample
Resolves: #1408
2019-06-14 22:55:38 +02:00
Vedran Pavic
17005c51a7 Add simple Redis SessionRepository implementation
See: #1408
2019-06-14 22:55:21 +02:00
Vedran Pavic
54859070f3 Extract RedisSessionMapper
See: #1408
2019-06-14 22:55:11 +02:00
Vedran Pavic
3d03c02924 Upgrade Spring Data to Moore-RC1
Resolves: #1436
2019-06-14 15:38:52 +02:00
Vedran Pavic
4f9d55feec Update integration tests 2019-06-13 18:36:49 +02:00
Vedran Pavic
7e7d08a99f Upgrade test dependencies 2019-06-13 18:32:27 +02:00
Vedran Pavic
f6a5bdacb8 Upgrade Hazelcast to 3.12.1
Resolves: #1452
2019-06-13 18:17:59 +02:00
Vedran Pavic
79de4f9a0d Upgrade Spring Framework to 5.2.0.M3
Resolves: #1414
2019-06-13 18:15:47 +02:00
Vedran Pavic
9a379fd5bc Upgrade Reactor to Dysprosium-M2
Resolves: #1416
2019-06-11 11:25:10 +02:00
Vedran Pavic
be58c00838 Separate "filtered" attribute for ERROR dispatch
Resolves: #1308
See: spring-projects/spring-framework#22989
2019-06-11 07:27:25 +02:00
Vedran Pavic
3475043bf0 Polish build
See: spring-projects/spring-security#6200
2019-06-11 07:27:25 +02:00
Rob Winch
c6470c6f48 Jenkinsfile performs git clean 2019-06-10 16:50:35 -05:00
Rob Winch
6faa67a64e Add nohttp 2019-06-10 14:55:06 -05:00
Vedran Pavic
340b614860 Upgrade samples to Spring Boot 2.2.0.M2
Resolves: #1445
2019-06-10 09:14:05 +02:00
Vedran Pavic
79b092d8f0 Align Selenium version used in samples with Spring Boot 2.2
See: #1445
2019-06-09 15:44:40 +02:00
Vedran Pavic
2e91024a56 Fix integration tests
See: #1033
2019-06-09 10:54:28 +02:00
Vedran Pavic
e359468abc Fix broken documentation links
See: #1447
2019-06-09 10:52:45 +02:00
Vedran Pavic
4ec6a9a08b Upgrade samples to Spring Boot 2.2.0.M3
Resolves: #1445
2019-06-06 21:55:07 +02:00
Vedran Pavic
084d1c7286 Simplify project structure
- harmonize module and directory names
- optimize Gradle settings
- remove unused Grails sample

Resolves: #1447
2019-06-06 21:12:30 +02:00
Vedran Pavic
a4ff3682f6 Migrate tests to JUnit 5
Resolves: #1033
2019-06-06 20:57:45 +02:00
Vedran Pavic
35f09d0da7 Start building against Reactor Dysprosium snapshots
See: #1416
2019-06-04 21:56:29 +02:00
Vedran Pavic
566b388b2f Align Checkstyle config with Spring Boot 2019-06-03 16:49:08 +02:00
Vedran Pavic
78b72f2d1b Save reactive Redis session on subscribe
This commit ensures ReactiveRedisOperationsSessionRepository#save does work only after subscribe. Without this, multiple invocations of #save over the course of same request can lead to race condition situations.

Resolves: #1399
2019-06-02 19:02:13 +02:00
Vedran Pavic
52f59a83e4 Update integration tests 2019-05-31 23:31:01 +02:00
Vedran Pavic
6809168541 Upgrade test dependencies 2019-05-31 23:13:28 +02:00
Vedran Pavic
02bb998f97 Start building against Spring Security 5.2 snapshots
See: #1437
2019-05-31 23:07:10 +02:00
Vedran Pavic
57ffb90a0c Start building against Spring Data Moore snapshots
See: #1436
2019-05-31 23:06:33 +02:00
Vedran Pavic
402272d5aa Start building against Spring Framework 5.2 snapshots
See: #1414
2019-05-31 23:05:38 +02:00
Vedran Pavic
1dbaffef5e Update Jenkinsfile 2019-05-27 20:28:34 +02:00
Vedran Pavic
8ae0e0b314 Update Jenkinsfile to use explicit JAVA_HOME 2019-05-27 07:27:34 +02:00
Rob Winch
d94d58d96b Redis save uses then
We need to ensure that the session id is changed before we save the
changes. Otherwise the rename of the session id will override the
changes we just made.

Fixes: gh-1428
2019-05-16 15:51:51 -05:00
Vedran Pavic
cc41ea5271 Ensure Redis session with immediate flush respects defaultMaxInactiveInterval
Resolves: #1409
2019-05-13 19:08:03 +02:00
Vedran Pavic
e6a88ccf4c Add Java 12 CI build
Resolves: #1422
2019-05-10 22:18:59 +02:00
Vedran Pavic
36f1cd5302 Revert "Add Java 12 CI build"
See: #1422
2019-05-10 09:39:24 +02:00
Vedran Pavic
3bd892ec16 Upgrade Gradle to 5.4.1 2019-05-10 09:25:46 +02:00
Vedran Pavic
dcd5342204 Add Java 12 CI build
Resolves: #1422
2019-05-10 09:24:29 +02:00
Vedran Pavic
48c32228de Upgrade Spring Framework to 5.2.0.M1
Resolves: #1414
2019-05-09 19:05:56 +02:00
Vedran Pavic
ec8ef595a2 Upgrade Reactor to Dysprosium-M1
Resolves: #1416
2019-05-09 18:49:24 +02:00
Vedran Pavic
b98c5216b4 Update configuration classes to use proxyBeanMethods=false
Resolves: #1345
2019-05-09 18:10:57 +02:00
Vedran Pavic
eb0f89a18a Start building against Spring Framework 5.2 snapshots
See: #1414
2019-05-09 18:03:56 +02:00
Vedran Pavic
83e0f4f24a Fix JdbcOperationsSessionRepository lazy deserialization
Resolves: #1411
2019-05-06 23:37:08 +02:00
Vedran Pavic
397d4e5ca1 Next development version 2019-04-12 20:51:00 +02:00
Vedran Pavic
f7b7737896 Release 2.2.0.M1 2019-04-12 20:38:14 +02:00
Vedran Pavic
2a5b221d70 Update integration tests 2019-04-12 20:14:12 +02:00
Vedran Pavic
5a462b3594 Resolve indexes for Hazelcast session on write
Resolves: #1145
2019-04-12 20:08:54 +02:00
Vedran Pavic
c252c00a85 Upgrade Spring Data to Moore-M3
Resolves: #1317
2019-04-11 15:49:31 +02:00
Vedran Pavic
2a7b6ec4f7 Upgrade Spring Security to 5.2.0.M1
Resolves: #1318
2019-04-11 07:39:26 +02:00
Vedran Pavic
044e5bbe3c Upgrade Spring Framework to 5.2.0.M1
Resolves: #1348
2019-04-10 16:05:35 +02:00
Vedran Pavic
f247a78c32 Revert Derby dependency upgrade 2019-04-10 09:23:02 +02:00
Vedran Pavic
c0f708d0b3 Upgrade Reactor to Californium-SR6 2019-04-09 21:31:58 +02:00
Vedran Pavic
c2a3ffdb00 Upgrade Hazelcast to 3.12
Resolves: #1403
2019-04-09 21:28:45 +02:00
Vedran Pavic
856a9b2c51 Update integration tests 2019-04-09 21:24:55 +02:00
Vedran Pavic
0d24033749 Upgrade test dependencies 2019-04-09 21:19:01 +02:00
Vedran Pavic
7a5f9bc29e Polish 2019-03-29 21:41:09 +01:00
Vedran Pavic
094ffe8a60 Avoid conflicts with user provided RedisMessageListenerContainer
Resolves: #1252
2019-03-29 21:27:56 +01:00
Vedran Pavic
55102aa8bb Update to latest Checkstyle DTDs 2019-03-29 21:27:56 +01:00
Vedran Pavic
f2e9634789 Fix Checkstyle config 2019-03-29 21:27:56 +01:00
Vedran Pavic
7dccade893 Upgrade spring-build-conventions to 0.0.25.RELEASE 2019-03-29 21:27:54 +01:00
Vedran Pavic
39408ff42a Upgrade Gradle to 5.3.1 2019-03-29 17:54:09 +01:00
Spring Operator
a5a3bc5d0b 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.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).

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

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

Fixes gh-1375
2019-03-22 10:24:07 -04:00
Spring Operator
90c08340fa 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.11.xsd with 3 occurrences migrated to:
  https://www.hazelcast.com/schema/config/hazelcast-config-3.11.xsd ([https](https://www.hazelcast.com/schema/config/hazelcast-config-3.11.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

Fixes gh-1374
2019-03-21 16:59:05 -04:00
Rob Winch
13208dd3b5 Update Checkstyle to https Apache 2 License
Issue gh-1369
2019-03-14 22:27:27 -05:00
Spring Operator
0975d4d47e 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 269 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:39:41 -05:00
Vedran Pavic
0c0bfa4414 Remove special handling for Servlet 3.0 and below
Closes: #1363
2019-03-14 21:59:45 +01:00
Vedran Pavic
8b40e8cce8 Rework RedisHttpSessionConfiguration to bean method invocations
Closes: #1362
2019-03-14 21:34:53 +01:00
Vedran Pavic
408f2da108 Start building against Spring Data Moore snapshots
See: #1317
2019-03-13 18:18:16 +01:00
Vedran Pavic
3b7bb7ec12 Start building against Spring Framework 5.2 snapshots
See: #1348
2019-03-13 18:18:16 +01:00
Vedran Pavic
4c849caccd Next development version 2019-03-13 18:09:34 +01:00
Vedran Pavic
ea84b8bd22 Upgrade samples to Spring Boot 2.1.3.RELEASE
Resolves: #1357
2019-03-12 18:29:26 +01:00
Jay Bryant
0c9fbedd05 Editing pass
I edited for spelling, grammar, punctuation, usage, and corporate voice, with the goal of making this content be consistent with our other content.
2019-02-15 13:33:51 -06:00
Rob Winch
08495b1321 Next Development Version 2019-02-13 19:55:46 -06:00
Rob Winch
60cd6d8be6 Release 2.1.4.RELEASE 2019-02-13 15:55:29 -06:00
Rob Winch
5503057523 Update to Spring Security 5.1.4
Fixes: gh-1329
2019-02-13 15:54:46 -06:00
Vedran Pavic
8fc2f1130a Update integration tests 2019-02-13 19:36:48 +01:00
Vedran Pavic
f5d63efcf1 Upgrade test dependencies 2019-02-13 19:32:40 +01:00
Vedran Pavic
ed76514f30 Upgrade spring-build-conventions to 0.0.23.RELEASE 2019-02-13 19:26:19 +01:00
Vedran Pavic
e1c0d62d33 Upgrade Reactor to Californium-SR5
Resolves: #1326
2019-02-13 19:24:42 +01:00
Vedran Pavic
8041358d18 Upgrade Spring Data to Lovelace-SR5
Resolves: #1325
2019-02-13 19:24:09 +01:00
Vedran Pavic
cdd85cb349 Upgrade Spring Framework to 5.1.5.RELEASE
Resolves: #1324
2019-02-13 19:23:46 +01:00
Vedran Pavic
381a07cb8c 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: #1270
2019-01-29 21:29:10 +01:00
Vedran Pavic
0e89539e20 Upgrade samples to Spring Boot 2.1.2.RELEASE
Resolves: #1318
2019-01-16 22:15:51 +01:00
Vedran Pavic
f00a1b9c6f Fix HazelcastSessionRepository to update TTL when maxInactiveInterval is changed
Resolves: #1300
2019-01-12 12:22:45 +01:00
Rob Winch
39cecb0902 Next Development Version 2019-01-10 21:36:58 -06:00
Rob Winch
c5fc4b57ad Release 2.1.3.RELEASE 2019-01-10 21:36:09 -06:00
Rob Winch
3b826e51a1 Update to Spring Security 5.1.3.RELEASE
Fixes: gh-1293
2019-01-10 21:34:44 -06:00
Vedran Pavic
67063ec0c6 Upgrade Gradle to 4.10.3 2019-01-10 16:22:39 +01:00
Vedran Pavic
72198e9e80 Update integration tests 2019-01-10 16:19:38 +01:00
Vedran Pavic
b3ee28b972 Upgrade test dependencies 2019-01-10 16:13:41 +01:00
Vedran Pavic
2d498bf69d Upgrade Spring Data to Lovelace-SR4
Resolves: #1292
2019-01-10 14:20:50 +01:00
Vedran Pavic
c365e4d941 Upgrade Spring Framework to 5.1.4.RELEASE
Resolves: #1291
2019-01-09 16:21:37 +01:00
Vedran Pavic
f7f8d4f6c0 Upgrade Reactor to Californium-SR4
Resolves: #1298
2019-01-08 17:59:54 +01:00
Vedran Pavic
fd5115fae5 Fix Spring Security integration docs sample 2019-01-08 08:15:08 +01:00
Vedran Pavic
a4c39fde9f Polish 2018-12-26 20:16:32 +01:00
Vedran Pavic
96391ce41a Fix assertion in Hazelcast tests 2018-12-24 20:59:06 +01:00
Vedran Pavic
d48eebea99 Upgrade Hazelcast to 3.11.1
Resolves: #1299
2018-12-20 18:52:27 +01:00
Vedran Pavic
57cd6c367d Upgrade samples to Spring Boot 2.1.1.RELEASE
Resolves: #1294
2018-12-17 21:12:38 +01:00
Roman Beskrovnyi
68f83b00eb Fix SessionRepositoryFilter Javadoc 2018-12-12 13:57:48 -06:00
Jeff
a4a5b529ef Fix RedisOperationsSessionRepository javadoc typos
Resolves: #1273
2018-12-04 16:35:56 +01:00
Vedran Pavic
f5ae38d94c Next development version 2018-11-28 19:43:47 +01:00
Vedran Pavic
b201ed971c Release 2.1.2.RELEASE 2018-11-28 19:38:37 +01:00
Vedran Pavic
70346b0a84 Upgrade test dependencies 2018-11-28 19:14:15 +01:00
Vedran Pavic
d4fd8b97b4 Upgrade Spring Security to 5.1.2.RELEASE
Resolves: #1261
2018-11-28 15:57:45 +01:00
Vedran Pavic
b3d01063d9 Upgrade Spring Data to Lovelace-SR3
Resolves: #1249
2018-11-27 15:20:19 +01:00
Vedran Pavic
124565306b Tweak Hazelcast session event tests
See: #1267
2018-11-27 13:21:16 +01:00
Vedran Pavic
f709a6c787 Upgrade Hazelcast to 3.11
Resolves: #1267
2018-11-27 11:29:48 +01:00
Vedran Pavic
c354927ef3 Upgrade spring-build-conventions to 0.0.22.RELEASE 2018-11-27 11:29:37 +01:00
Vedran Pavic
2db79e2bb8 Upgrade Spring Framework to 5.1.3.RELEASE
Resolves: #1248
2018-11-27 11:29:10 +01:00
Vedran Pavic
3480c65c2b Polish 2018-11-26 18:24:14 +01:00
Vedran Pavic
e0dc0262ef Make SessionUpdateEntryProcessor public
Resolves: #1260
2018-11-26 18:18:18 +01:00
Vedran Pavic
3b7da0c370 Update integration tests 2018-11-26 17:54:57 +01:00
Vedran Pavic
72984f9ca6 Upgrade test dependencies 2018-11-26 17:42:00 +01:00
Vedran Pavic
8a4872b919 Improve exception asserts 2018-11-26 10:02:40 +01:00
Vedran Pavic
6b6c6f27df Upgrade Reactor to Californium-SR3
Resolves: #1262
2018-11-23 19:26:15 +01:00
Vedran Pavic
640bee3fc4 Update Jenkins pipeline
- add JDK 9 stage (only unit tests)
- update JDK 10 stage to only run unit tests
2018-11-22 23:11:31 +01:00
Vedran Pavic
3bfdb9be93 Polish contribution
Resolves: #1243
2018-11-02 22:28:34 +01:00
Josh Cummings
c8f3d1a1ec 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: #1243
2018-11-02 22:28:34 +01:00
Vedran Pavic
11ad1db6e7 Update integration tests 2018-11-01 17:50:35 +01:00
Vedran Pavic
7b87128db6 Upgrade test dependencies 2018-11-01 17:28:06 +01:00
Vedran Pavic
bf861933ed Upgrade samples to Spring Boot 2.1.0.RELEASE
Resolves: #1240
2018-11-01 17:24:59 +01:00
Rob Winch
979e91256d Next Development Version 2018-10-29 10:42:02 -05:00
Rob Winch
05986d68b2 Release 2.1.1.RELEASE 2018-10-29 10:08:49 -05:00
Rob Winch
e17b047800 Update to Spring Data Lovelace-SR2
Fixes: gh-1234
2018-10-29 10:07:26 -05:00
Vedran Pavic
5ab2424b14 Upgrade Spring Framework to 5.1.2.RELEASE
Resolves: #1233
2018-10-29 13:04:47 +01:00
Vedran Pavic
196919efbb Upgrade Reactor to Californium-SR2
Resolves: #1235
2018-10-29 07:37:17 +01:00
Vedran Pavic
717e16cb71 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: #1229
2018-10-26 19:55:37 +02:00
Rob Winch
5f1b7d6722 Next Development Version 2018-10-15 20:05:12 -05:00
Rob Winch
4d3a01919c Release 2.1.0.RELEASE 2018-10-15 20:04:27 -05:00
Rob Winch
e408d7f557 Update to Spring Security 5.1.1.RELEASE
Fixes: gh-1222
2018-10-15 20:04:04 -05:00
Vedran Pavic
f34acebf84 Upgrade integration tests 2018-10-15 18:42:12 +02:00
Vedran Pavic
1aab3e8285 Upgrade test dependencies 2018-10-15 18:35:18 +02:00
Vedran Pavic
c3528996d2 Upgrade Hazelcast to 3.10.6
Resolves: #1223
2018-10-15 18:34:44 +02:00
Vedran Pavic
3ccc3eb6e1 Upgrade Reactor to Californium-SR1
Resolves: #1221
2018-10-15 18:34:10 +02:00
Vedran Pavic
de76be95ac Upgrade Spring Data to Lovelace-SR1
Resolves: #1220
2018-10-15 18:33:25 +02:00
Vedran Pavic
bc127ab3fc Upgrade Spring Framework to 5.1.1.RELEASE
Resolves: #1219
2018-10-15 18:32:47 +02:00
Vedran Pavic
3e9f6a35c4 Fix root project name 2018-10-01 22:46:10 +02:00
Vedran Pavic
49daa3a9c7 Polish 2018-09-26 14:16:02 +02:00
Vedran Pavic
a67bd634d9 Disable network join in Hazelcast samples 2018-09-26 14:16:00 +02:00
Vedran Pavic
2762f001bf Add Oracle integration tests 2018-09-25 19:10:15 +02:00
Vedran Pavic
93aee206fb 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: #1203
See also: #1031
2018-09-25 18:45:02 +02:00
Vedran Pavic
3df3b30117 Upgrade Testcontainers to 1.9.1 2018-09-25 18:31:52 +02:00
Vedran Pavic
5fb0c4dd35 Improve JDBC integration tests 2018-09-24 06:30:47 +02:00
Vedran Pavic
6fbce6e3e8 Next development version 2018-09-21 21:27:42 +02:00
Vedran Pavic
a3fd05326a Release 2.1.0.RC1 2018-09-21 21:26:28 +02:00
Vedran Pavic
4c6dc976b3 Upgrade Testcontainers to 1.9.0-rc2 2018-09-21 19:22:12 +02:00
Vedran Pavic
58ae28b0a0 Fix SpringSessionRememberMeServices documentation example
Resolves: #1157
2018-09-21 19:05:33 +02:00
Vedran Pavic
3e98ecf234 Upgrade Spring Security to 5.1.0.RELEASE
Resolves: #1188
2018-09-21 19:01:15 +02:00
Vedran Pavic
41ed429f98 Upgrade Spring Data to Lovelace-RELEASE
Resolves: #1190
2018-09-21 19:00:38 +02:00
Vedran Pavic
def15b05ca Upgrade Spring Framework to 5.1.0.RELEASE
Resolves: #1187
2018-09-21 11:10:33 +02:00
Vedran Pavic
eae8592f2b Upgrade integration tests 2018-09-20 19:48:33 +02:00
Vedran Pavic
81460ede09 Make SessionUpdateEntryProcessor implement Offloadable
Resolves: #1204
2018-09-20 19:31:55 +02:00
Vedran Pavic
ca4ec9a557 Upgrade test dependencies 2018-09-20 19:23:24 +02:00
Vedran Pavic
fd2165f471 Upgrade Hazelcast to 3.10.5
Resolves: #1206
2018-09-20 19:23:24 +02:00
Vedran Pavic
ad1e57a1fe Upgrade Gradle to 4.10.2 2018-09-20 19:15:26 +02:00
Vedran Pavic
0ffcaa2d35 Upgrade Reactor to Californium-RELEASE
Resolves: #1189
2018-09-20 11:45:33 +02:00
Vedran Pavic
b61937def7 Polish contribution
Resolves: #1133
2018-09-19 23:53:38 +02:00
Craig Andrews
c523fb591d Deserialize attributes lazily in JdbcOperationsSessionRepository
Instead of deserializing all of the session attributes as they are read from the database, deserialize as #getAttribute requests them.

See: #1133
2018-09-19 23:48:15 +02:00
Vedran Pavic
227fab2e42 Adjust CI build timeouts 2018-09-19 00:45:20 +02:00
Vedran Pavic
7f7815d80c Upgrade spring-build-conventions to 0.0.19.RELEASE 2018-09-19 00:01:06 +02:00
Vedran Pavic
002136bad4 Align WebSession#save implementations with API
Closes gh-1135
2018-09-18 23:58:59 +02:00
Vedran Pavic
1085661984 Enable integration tests for JDK 10 and 11 builds
See: #1196, #1197
2018-09-18 20:04:23 +02:00
Vedran Pavic
12bb0741bb Add Java 11 CI build
Closes gh-1197
2018-09-17 18:02:07 +02:00
Vedran Pavic
eecdcb49d9 Remove node designation from JDK 10 build
See gh-1196
2018-09-17 17:59:40 +02:00
Vedran Pavic
3e1a22102d Ensure compatibility with Java 9 and 10
Closes gh-1196
2018-09-16 22:13:56 +02:00
Vedran Pavic
9f6e791e5d Upgrade samples to Spring Boot 2.1.0.M3
Closes gh-1195
2018-09-13 21:04:43 +02:00
Vedran Pavic
efc35eddad Upgrade Gradle to 4.10.1 2018-09-13 20:59:49 +02:00
Vedran Pavic
4c37ec9f4a Update Jenkinsfile to specify node label 2018-09-13 18:08:17 +02:00
Vedran Pavic
1a3da5944d Polish
See gh-1128
2018-09-13 08:55:13 +02:00
Vedran Pavic
5d0775b802 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-1128
2018-09-12 23:07:52 +02:00
Vedran Pavic
603a258172 Upgrade Testcontainers to 1.9.0-rc1 2018-09-11 23:06:10 +02:00
Vedran Pavic
22ebe65931 Next development version 2018-09-10 22:42:32 +02:00
Vedran Pavic
55033bcb64 Release 2.1.0.M3 2018-09-10 22:40:21 +02:00
Vedran Pavic
57955b7d7b Polish
See gh-1111
2018-09-10 17:03:10 +02:00
Vedran Pavic
d5da38f2e0 Upgrade test dependencies 2018-09-10 16:56:08 +02:00
Vedran Pavic
6cc4bcd13d 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-1111
2018-09-09 23:55:27 +02:00
Vedran Pavic
dc43f5bd2d Upgrade Spring Security to 5.1.0.RC2
Closes gh-1171
2018-09-07 23:48:18 +02:00
Vedran Pavic
7584cbd54c Upgrade Spring Framework to 5.1.0.RC3
Closes gh-1170
2018-09-07 17:40:18 +02:00
Vedran Pavic
0db1160dc4 Upgrade Reactor to Californium-RC1
Closes gh-1172
2018-09-07 07:48:08 +02:00
Vedran Pavic
10a18366f9 Update integration tests 2018-09-07 07:46:10 +02:00
Vedran Pavic
7ea5e2f3ee Upgrade test dependencies 2018-09-06 21:15:47 +02:00
Vedran Pavic
d3134ad065 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 #1137
2018-09-04 23:07:27 +02:00
Vedran Pavic
6208d0298d Upgrade Gradle to 4.10 2018-09-04 21:57:04 +02:00
Vedran Pavic
c031ee278d Add javadoc for RedisOperationsSessionRepository#getSessionRedisOperations
Closes #1175
2018-09-03 23:29:50 +02:00
Vedran Pavic
8267a90fcc Polish contribution
See #1173
2018-09-03 23:28:14 +02:00
Johnny Lim
2113b330a7 Add @since for ReactiveRedisOperationsSR.getSessionRedisOperations() 2018-08-31 10:29:09 -05:00
Vedran Pavic
c4ac68b777 Fix Jenkinsfile 2018-08-27 09:26:55 +02:00
Vedran Pavic
0be2759e68 Fix Jenkinsfile 2018-08-27 08:24:36 +02:00
Vedran Pavic
1181e52bb0 Upgrade spring-build-conventions to 0.0.18.RELEASE 2018-08-24 23:50:23 +02:00
Vedran Pavic
5277d945ed Upgrade samples to Spring Boot 2.1.0.M2
Closes gh-1168
2018-08-22 18:31:30 +02:00
Rob Winch
1fc0162fe9 Fix settings.gradle on Windows
Fixes: gh-1167
2018-08-22 10:23:29 -05:00
Vedran Pavic
9df259b1ae Next development version 2018-08-21 06:34:09 +02:00
Vedran Pavic
0c2f756533 Release 2.1.0.M2 2018-08-21 06:33:12 +02:00
Vedran Pavic
de16c304ea Add support using JDBC repository without transactions
Closes gh-1046
2018-08-21 06:05:52 +02:00
Vedran Pavic
3ce3962ebd Upgrade Spring Security to 5.1.0.RC1
Closes gh-1144
2018-08-20 20:41:38 +02:00
Vedran Pavic
3c4a309a0f Upgrade Spring Data to Lovelace-RC2
Closes gh-1143
2018-08-20 11:51:28 +02:00
Vedran Pavic
38de434158 Add support for @SpringSessionRedisOperations in reactive Redis repository
Closes gh-1164
2018-08-20 07:23:23 +02:00
Vedran Pavic
7ef0faf259 Update integration tests 2018-08-20 06:31:10 +02:00
Vedran Pavic
f65cee0a7b Upgrade dependencies 2018-08-20 06:31:10 +02:00
Vedran Pavic
a2cd1e37fa Add support for configuring custom RedisSerializer in reactive config
Closes gh-1149
2018-08-20 06:31:09 +02:00
Vedran Pavic
b768042506 Upgrade Spring Framework to 5.1.0.RC2
Closes gh-1141
2018-08-17 12:21:48 +02:00
Vedran Pavic
3140bd06b2 Add FindByIndexNameSessionRepository#findByPrincipalName default method
Closes gh-1158
2018-08-17 08:04:09 +02:00
Vedran Pavic
172c18d666 Upgrade Reactor to Californium-M2
Closes gh-1142
2018-08-16 07:13:59 +02:00
Vedran Pavic
7fdf2876b2 Polish 2018-08-13 07:44:47 +02:00
Vedran Pavic
87c2e53b5a 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-1031
2018-08-13 06:28:42 +02:00
Vedran Pavic
268ba663e5 Remove SpringSessionWebSessionStore#storeSession
Closes gh-1150
2018-08-09 16:32:14 +02:00
Vedran Pavic
3f4873f0eb Simplify tests related to SameSite cookie directive support
Closes gh-1147
2018-08-03 23:20:55 +02:00
Vedran Pavic
644239ee14 Start building against Spring Framework 5.1.0 snapshots
See gh-1141
2018-08-03 23:20:52 +02:00
Johnny Lim
97e52de41b Make MapSession.originalId final
Closes gh-1146
2018-08-02 18:46:59 +02:00
Vedran Pavic
f4bbc18f94 Fix Jenkinsfile 2018-08-01 02:00:48 +02:00
Vedran Pavic
dfe216b482 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 01:01:32 +02:00
Vedran Pavic
a976c9dd6d Upgrade samples to Spring Boot 2.1.0.M1
Closes gh-1139
2018-07-31 22:22:50 +02:00
Vedran Pavic
deb2863507 Next development version 2018-07-30 02:49:33 +02:00
Vedran Pavic
7bdb3f6ded Release 2.1.0.M1 2018-07-30 02:36:01 +02:00
Vedran Pavic
7d3472f55d Remove Spring IO check from build 2018-07-30 02:31:00 +02:00
Vedran Pavic
00465a6f00 Add support for SameSite cookie directive
Closes gh-1005
2018-07-30 02:13:57 +02:00
Vedran Pavic
ad35d7ca30 Add support for HttpSessionBindingListener
Closes gh-1018
2018-07-29 08:09:00 +02:00
Vedran Pavic
18e9ab4c0f Polish 2018-07-27 13:14:04 +02:00
Vedran Pavic
1c9a6d3e5d Upgrade Spring Security to 5.1.0.M2
Closes gh-1125
2018-07-27 13:13:19 +02:00
Vedran Pavic
d2936ed0b4 Upgrade dependencies 2018-07-27 11:10:14 +02:00
Vedran Pavic
cdf6089ccd Upgrade Spring Data to Lovelace-RC1
Closes gh-1126
2018-07-26 23:14:16 +02:00
Vedran Pavic
1ca8a6476a Upgrade Spring Framework to 5.1.0.RC1
Closes gh-1124
2018-07-26 23:13:31 +02:00
Vedran Pavic
cf926045dc Upgrade Reactor to Californium-M1
Closes gh-1127
2018-07-25 22:05:19 +02:00
Vedran Pavic
7123df8656 Remove MapSession#setOriginalId
Closes gh-1100
2018-07-25 22:03:19 +02:00
Rob Winch
096a5683cb Spring Session Core 2.1.0.BUILD-SNAPSHOT 2018-07-25 10:32:29 -07:00
Vedran Pavic
db31527c8c Add logging for errors decoding Base64 cookies
Closes gh-1117
2018-07-24 23:37:52 +02:00
Vedran Pavic
3d2a742328 Use Spring Java Format Checkstyle
Closes gh-1113
2018-07-23 15:16:35 +02:00
Vedran Pavic
7ac6e458e0 Update integration tests 2018-07-23 12:15:14 +02:00
Vedran Pavic
9adf0a6e0c Upgrade spring-build-conventions to 0.0.17.RELEASE 2018-07-18 09:38:02 +02:00
Vedran Pavic
58219fa016 Upgrade Gradle to 4.9 2018-07-18 08:15:38 +02:00
Vedran Pavic
83cbff5ce2 Improve support for Hazelcast client-server topology
This commit improves support for use of Spring Session with Hazelcast's client-server topology by ensuring SessionUpdateEntryProcessor is easier to serialize to the cluster. This is done by refactoring SessionUpdateEntryProcessor from static inner class of HazelcastSessionRepository to a dedicated class, therefore minimizing the dependencies to other Spring Session components.

Closes gh-1101
2018-07-17 21:42:33 +02:00
Vedran Pavic
936fc853df Ensure Session#getAttributeNames implementations return a copy
Currently, Session#getAttributeNames implementations, by delegating to MapSession, all return a session attribute map's key set. This causes ConcurrentModificationException when an attempt to modify session attributes is made while iterating over the returned attribute names.

Closes gh-1120
2018-07-17 15:05:03 +02:00
Vedran Pavic
dba475c48f Invalidate session before clearing session store
Closes gh-1114
2018-07-13 10:50:49 +02:00
Vedran Pavic
9956e91b93 Upgrade samples to Spring Boot 2.0.3.RELEASE
Closes gh-1107
2018-07-13 10:50:49 +02:00
Dave Syer
c902981eba Fix garbled syntax relating to dropped APIs 2018-07-11 08:07:58 -05:00
Vedran Pavic
2e26c6e9d3 Upgrade Gradle to 4.8.1 2018-06-21 22:53:20 +02:00
Vedran Pavic
b9cd3865c5 Next development version 2018-06-13 23:13:05 +02:00
Vedran Pavic
1f7232f12e Release 2.0.4.RELEASE 2018-06-13 23:06:09 +02:00
Vedran Pavic
03f0a571b6 Upgrade Spring Data to Kay-SR8
See gh-1094
2018-06-13 22:44:12 +02:00
Vedran Pavic
63a215f73b Disable network join in Hazelcast integration tests 2018-06-13 17:01:57 +02:00
Vedran Pavic
8dac35cf73 Fix session event handling in HazelcastSessionRepository
Previously, invoking HttpServletRequest#changeSessionId on session backed by HazelcastSessionRepository generated generated invalid session destroyed and session created events. This was due to use of IMap#remove and IMap#set when handling the change session id.

This commit improves change session id handling to prevent publishing invalid events by using IMap#delete instead of IMap#remove and keeping track of originally assigned session id.

Closes gh-1077
2018-06-13 16:12:28 +02:00
Vedran Pavic
19b8583d65 Adapt to Spring Framework deprecations
See gh-1092
2018-06-13 05:59:50 +02:00
Vedran Pavic
6de0f44241 Upgrade dependencies 2018-06-13 05:41:23 +02:00
Vedran Pavic
60d6120b9c Upgrade Spring Security to 5.0.6.RELEASE
Closes gh-1095
2018-06-13 05:23:56 +02:00
Vedran Pavic
3bc899e695 Upgrade Spring Framework to 5.0.7.RELEASE
Closes gh-1092
2018-06-12 17:41:16 +02:00
Vedran Pavic
c2fe999d6c Update reference manual to mention BOM module
Closes gh-1099
2018-06-12 12:02:48 +02:00
Vedran Pavic
d214971e72 Upgrade Reactor to Bismuth-SR10
Closes gh-1093
2018-06-11 15:21:55 +02:00
Vedran Pavic
f4704293a1 Update integration tests 2018-06-08 16:48:31 +02:00
Vedran Pavic
a8c4f65903 Upgrade spring-build-conventions to 0.0.16.RELEASE 2018-06-05 21:35:48 +02:00
Vedran Pavic
4a52de0c18 Upgrade Gradle to 4.8 2018-06-05 21:34:19 +02:00
Vedran Pavic
63f105082a Optimize Redis integration tests
This commit ensures that Redis Testcontainers used for integration testing are managed by Spring to ensure proper ordering on shutdown.

Previously, Redis Testcontainer was closed before LettuceConnectionFactory which caused pending commands to hang and added a lot of wait to project build.

Closes gh-1086
2018-06-01 11:50:01 +02:00
Vedran Pavic
f55b793185 Remove Servlet API version check from DefaultCookieSerializer
Closes gh-1079
2018-05-31 10:42:44 +02:00
Vedran Pavic
6d027900ee Fix caching of requested session in SessionRepositoryFilter
Closes gh-1076
2018-05-15 10:03:41 +02:00
Vedran Pavic
42818a1b90 Improve update handling in HazelcastSessionRepository
This commit improves HazelcastSessionRepository.SessionUpdateEntryProcessor to avoid NPE in scenario where save operation was invoked for session that was already deleted.

See gh-1076
2018-05-15 08:16:20 +02:00
Vedran Pavic
b6348736ac Polish contribution
Closes gh-1070
2018-05-14 10:38:27 +02:00
Craig Andrews
60581c6427 Fix delta handling in JdbcOperationsSessionRepository
See gh-1070
2018-05-13 21:05:34 +02:00
Craig Andrews
836ea12e93 Upgrade samples to Spring Boot 2.0.2.RELEASE 2018-05-11 09:34:46 -05:00
Rob Winch
670148f182 Next Development Version 2018-05-08 14:45:39 -05:00
Rob Winch
a39295c02b Release 2.0.3 2018-05-08 13:57:03 -05:00
Vedran Pavic
02cd5a6301 Upgrade test dependencies 2018-05-08 19:20:48 +02:00
Vedran Pavic
5824566621 Upgrade Spring Security to 5.0.5.RELEASE
Closes gh-1060
2018-05-08 18:43:03 +02:00
Vedran Pavic
b2711600e2 Polish contribution
Closes gh-1014
2018-05-08 17:35:49 +02:00
Ivan Sopov
06eb768721 Remove redundant index in JDBC schema scripts
See gh-1014
2018-05-08 17:33:16 +02:00
Vedran Pavic
fb05fa70c7 Upgrade Hazelcast to 3.9.4
Closes gh-1067
2018-05-08 17:29:28 +02:00
Vedran Pavic
1e93fe87db Upgrade test dependencies 2018-05-08 17:14:07 +02:00
Vedran Pavic
e67f84c6b6 Upgrade Spring Data to Kay-SR7
See gh-1059
2018-05-08 16:27:17 +02:00
Vedran Pavic
dfb2f2f334 Upgrade Reactor to Bismuth-SR9
See gh-1057
2018-05-08 16:26:27 +02:00
Vedran Pavic
c8e9630fdd Upgrade Spring Framework to 5.0.6.RELEASE
See gh-1058
2018-05-08 16:25:34 +02:00
Vedran Pavic
751375338c Optimize session resolution in SessionRepositoryFilter
This commit optimizes SessionRepositoryFilter to avoid multiple retrievals of session from SessionRepository.

Closes gh-1048
2018-05-04 21:26:35 +02:00
Vedran Pavic
538712d162 Fix compilation warnings 2018-05-04 18:05:39 +02:00
Vedran Pavic
941fdb46f2 Replace use of Test.expected with AssertJ
See gh-1032
2018-05-04 18:05:38 +02:00
Vedran Pavic
bb1c099094 Optimize batch operations in JdbcOperationsSessionRepository
This commit optimizes session attribute saving by ensuring batch updates are used whenever possible. To make this possible, delta now tracks operations for each attribute change in order to be able to deduce SQL operation.

Additionally, if there is only a single attribute change, regular update is executed rather than batch operation.

Closes gh-1051
2018-05-04 16:33:47 +02:00
Vedran Pavic
1d1253e643 Rename expiration key on changeSessionId in RedisOperationsSessionRepository
This commit ensures existing expiration key is renamed on changeSessionId operation in RedisOperationsSessionRepository. Previously, this key wasn't renamed which caused invalid invocations of SessionDestroyedEvent handling when key expired.

Closes gh-1029
2018-04-20 23:08:50 +02:00
Vedran Pavic
0e7e2eaf5c Upgrade samples to Spring Boot 2.0.1.RELEASE
Closes gh-1061
2018-04-20 15:58:19 +02:00
Vedran Pavic
e601e03e1e Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2018-04-20 15:56:30 +02:00
Vedran Pavic
2c81e50b5e Upgrade Spring Security to 5.0.4.RELEASE
Closes gh-1060
2018-04-20 15:18:30 +02:00
Vedran Pavic
ac5ff996f4 Upgrade Spring Data to Kay-SR6
Closes gh-1059
2018-04-20 15:17:58 +02:00
Vedran Pavic
44130cba80 Upgrade Spring Framework to 5.0.5.RELEASE
Closes gh-1058
2018-04-20 15:17:18 +02:00
Vedran Pavic
2cd8063c7c Upgrade Reactor to Bismuth-SR8
Closes gh-1057
2018-04-20 15:16:41 +02:00
Vedran Pavic
f42a6c7d1c Upgrade Gradle to 4.7 2018-04-20 14:10:54 +02:00
Vedran Pavic
6c2f6c26cc Update integration tests
- upgrade TestContainers to 1.7.1
- update Docker images
- improve MariaDB/MySQL tests to use UTF-8

Closes gh-1034
2018-04-20 14:10:52 +02:00
Vedran Pavic
91b4efc5bd Fix attribute mapping in ReactiveRedisOperationsSessionRepository
This commit ensures that attributes with null values are correctly mapped to session on retrieval from Redis.

Closes gh-1035
2018-04-19 15:31:08 +02:00
Vedran Pavic
6f8359ba16 Fix lastAccessedTime handling in SpringSessionWebSessionStore
This commit ensures lastAccessedTime is updated when session is retrieved, as per WebSessionStore API.

Closes gh-1039
2018-04-19 12:11:16 +02:00
Vedran Pavic
62bfeb3f05 Fix ReactiveRedisOperationsSessionRepository tests 2018-04-19 11:08:45 +02:00
Vedran Pavic
2395582fe6 Optimize session retrieval in JdbcOperationsSessionRepository
Previously, SessionResultSetExtractor used JdbcSession.setAttribute which had a side effect of freshly loaded session potentially having a non-empty delta and/or changed flag set. This commit optimizes session retrieval to invoke setAttribute directly on the delegate, therefore preventing unnecessary modifications of delta and change flags.

Closes gh-1042
2018-04-16 08:58:16 +02:00
Vedran Pavic
5173026aa8 Improve RedisOperationsSessionRepository tests 2018-04-16 08:52:43 +02:00
Vedran Pavic
d97ad2ca3e Polish 2018-03-31 08:32:18 +02:00
Vedran Pavic
a780ee0264 Replace use of ExpectedException rule with AssertJ
Closes gh-1032
2018-03-31 08:32:16 +02:00
Vedran Pavic
d8e7a2aa9f Add support for EditorConfig 2018-03-26 19:06:35 +02:00
Rob Winch
45b18dec84 Add CVE Reporting to Issue Template 2018-03-20 22:43:50 -05:00
Rob Winch
ec5406fb01 Add CVE Reporting in PR Template 2018-03-20 22:43:27 -05:00
Vedran Pavic
3c2f0fd485 Fix broken links in Spring Boot samples guides
Closes gh-1023
2018-03-20 10:57:03 +01:00
Vedran Pavic
cdfa557442 Update guides for Spring Boot based samples
Closes gh-1025
2018-03-20 10:44:51 +01:00
Vedran Pavic
edc8a7efff Upgrade Spring Boot to 2.0.0.RELEASE
Closes gh-1007
2018-03-09 07:23:38 +01:00
Vedran Pavic
a7a30dad30 Polish contribution
Closes gh-1009
2018-03-09 07:23:38 +01:00
Josh Cummings
be1d3d30a8 Upgrade Gradle to 4.6
See gh-1009
2018-03-09 07:23:28 +01:00
Vedran Pavic
010aa5f013 Next development version 2018-02-20 14:28:45 +01:00
Vedran Pavic
bfcb4afef7 Release 2.0.2.RELEASE 2018-02-20 14:24:54 +01:00
Vedran Pavic
72a902009e Upgrade spring-build-conventions to 0.0.13.RELEASE 2018-02-20 07:34:06 +01:00
Vedran Pavic
1e799f211f Upgrade Spring Security to 5.0.2.RELEASE
Closes gh-998
2018-02-20 07:32:35 +01:00
Vedran Pavic
90599b9bd3 Upgrade Spring Data to Kay-SR4
Closes gh-997
2018-02-19 22:29:17 +01:00
Vedran Pavic
8d7136072a Upgrade Spring Framework to 5.0.4.RELEASE
Closes gh-996
2018-02-19 13:05:58 +01:00
Vedran Pavic
4f0f3806a2 Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2018-02-19 09:14:56 +01:00
Vedran Pavic
a18037759c Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2018-02-19 09:06:26 +01:00
Vedran Pavic
eb479af1d4 Upgrade Reactor to Bismuth-SR6
Closes gh-999
2018-02-16 19:56:49 +01:00
Vedran Pavic
d0b472e8e2 Ignore SQL Server integration tests 2018-02-12 20:22:39 +01:00
Vedran Pavic
17ee9d51f2 Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2018-02-12 19:07:59 +01:00
Vedran Pavic
003996a1b3 Upgrade Gradle to 4.5.1 2018-02-06 15:30:09 +01:00
Vedran Pavic
13c0e325b4 Adapt to Spring WebSocket configuration deprecations
Closes gh-994
2018-02-06 15:30:09 +01:00
Vedran Pavic
7acdeffe22 Remove outdated sample docs
Closes gh-989
2018-02-06 15:30:09 +01:00
Vedran Pavic
de03b20619 Upgrade Spring Boot to 2.0.0.RC1
Closes gh-988
2018-02-06 15:30:02 +01:00
Vedran Pavic
becee53dbf Restore CookieSerializer.CookieValue constructor visibility
Closes gh-978
2018-02-05 19:11:08 +01:00
Vedran Pavic
4eb64e8140 Next development version 2018-01-25 18:52:21 +01:00
Vedran Pavic
e520ea237d Release 2.0.1.RELEASE 2018-01-25 18:46:29 +01:00
Vedran Pavic
175e05dcda Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2018-01-25 16:02:01 +01:00
Vedran Pavic
bb427ff1af Upgrade dependencies 2018-01-25 15:57:19 +01:00
Vedran Pavic
0a65b82373 Upgrade Spring Security to 5.0.1.RELEASE
Closes gh-974
2018-01-25 15:13:00 +01:00
Vedran Pavic
e25c64efae Upgrade Spring Data to Kay-SR3
Closes gh-975
2018-01-25 15:12:04 +01:00
Vedran Pavic
43fcba65c4 Ignore SQL Server integration tests 2018-01-25 15:10:59 +01:00
Vedran Pavic
1cc2c83f36 Polish 2018-01-25 15:10:53 +01:00
Vedran Pavic
0941358807 Upgrade Spring Framework to 5.0.3.RELEASE
Closes gh-973
2018-01-23 10:52:39 +01:00
Vedran Pavic
7d3698515e Upgrade Reactor to Bismuth-SR5
Closes gh-976
2018-01-23 07:32:38 +01:00
Vedran Pavic
d382603445 Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2018-01-23 07:31:32 +01:00
Vedran Pavic
22e3b5ce38 Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2018-01-22 08:17:52 +01:00
Vedran Pavic
ebd4b349d2 Fix run commands in samples documentation
Closes gh-969
2018-01-18 22:27:09 +01:00
Vedran Pavic
ffa1bca898 Update Redis configuration to use bean classloader
Spring Session 2.0 made changes to Redis configuration facilities so that the `RedisTemplate` used by `RedisOperationsSessionRepository` isn't exposed as a bean anymore. This has a consequence that bean `ClassLoader` isn't applied automatically which causes issues in Spring Boot applications that use DevTools.

This commit restores the previous behavior by updating Redis configuration classes to implement `BeanClassLoaderAware` callback and apply the application `ClassLoader` to `RedisTemplate`. The analogous change was made to reactive Redis configuration.

Closes gh-968
2018-01-18 22:27:02 +01:00
Rob Winch
d0ee9fd16a Use deployArtifacts 2018-01-09 15:13:37 -06:00
Rob Winch
7a631fe414 Next development version 2018-01-09 14:40:10 -06:00
Rob Winch
d217077dec Release 2.0.0.RELEASE 2018-01-09 14:39:16 -06:00
Vedran Pavic
a9b3ce034b Update readme 2018-01-09 20:31:29 +01:00
Vedran Pavic
1ba434a357 Document ReactiveMapSessionRepository map requirements
Closes gh-842
2018-01-09 20:19:22 +01:00
Vedran Pavic
45807998f6 Update documentation for 2.0
Closes gh-199
Closes gh-826
Closes gh-878
Closes gh-893
2018-01-09 13:04:59 -06:00
Vedran Pavic
2f49a8ac25 Ignore SQL Server integration tests
This commit temporarily disables SQL Server integration tests due to frequent container startup failures on Jenkins.

See gh-959
2018-01-09 14:21:48 +01:00
Vedran Pavic
e364511c7e Polish contribution
Closes gh-965
2018-01-08 17:38:29 +01:00
Johnny Lim
79ccbe7066 Polish
See gh-965
2018-01-08 17:38:06 +01:00
Vedran Pavic
1edce117aa Fix Redis change session id handling
This commit updates logic around changing session id in `RedisOperationsSessionRepository` to properly handle updates for new sessions i.e. ones that haven't been saved yet.

Previously, the logic skipped both Redis rename operation and replacement of session id within the current session holder object, which led to no such key errors on subsequent save operation which still observed the session id as changed.

Closes gh-962
2018-01-05 12:52:12 +01:00
Vedran Pavic
c0f4c7f381 Fix reactive Redis change session id handling
This commit updates logic around changing session id in `ReactiveRedisOperationsSessionRepository` to properly handle updates for new sessions i.e. ones that haven't been saved yet.

Previously, the logic skipped both Redis rename operation and replacement of session id within the current session holder object, which led to no such key errors on subsequent save operation which still observed the session id as changed.

Closes gh-954
2018-01-05 12:22:28 +01:00
Vedran Pavic
7fa07b2973 Upgrade Gradle to 4.4.1 2018-01-02 22:37:09 +01:00
Vedran Pavic
3252b38c87 Add Microsoft SQL Server integration tests
Closes gh-959
2018-01-02 22:37:09 +01:00
Vedran Pavic
c4daeff3d8 Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2018-01-02 21:42:22 +01:00
Vedran Pavic
2fccca1158 Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2018-01-02 21:12:55 +01:00
Vedran Pavic
81798c36f6 Upgrade Gradle to 4.4 2017-12-11 23:58:33 +01:00
Vedran Pavic
27283e29d5 Optimize HazelcastSessionRepository.SessionUpdateEntryProcessor
Closes gh-947
2017-12-01 23:20:19 +01:00
Vedran Pavic
77bb9dfdb1 Upgrade Spring Boot to 2.0.0.M7
Closes gh-930
2017-11-30 12:45:00 +01:00
Rob Winch
c874592323 Next Development Version 2.0.0.BUILD-SNAPSHOT 2017-11-27 21:54:05 -06:00
Rob Winch
676f0e474e Release 2.0.0.RC2 2017-11-27 21:53:36 -06:00
Rob Winch
e5ec612771 Update to Spring Security 5.0.0.RELEASE
Fixes gh-926
2017-11-27 21:53:00 -06:00
Vedran Pavic
280d5c5a77 Refactor JDBC configuration
Closes gh-942
2017-11-27 22:21:40 +01:00
Vedran Pavic
6a370b1ef8 Refactor Redis configuration
Closes gh-941
2017-11-27 22:21:40 +01:00
Vedran Pavic
41de1b087a Refactor Hazelcast configuration
Closes gh-938
2017-11-27 22:21:40 +01:00
Vedran Pavic
6188fe68b7 Improve session event handling
This commit removes constructor that takes session id instead of session object for the entire `AbstractSessionEvent` hierarchy.

The ability to create `AbstractSessionEvent` instances with no underlying session object leads to NPE when interacting with `HttpSession` obtained from `HttpSessionEvent`.

See gh-499
Closes gh-939
2017-11-27 22:21:40 +01:00
Rob Winch
ed328ff4b1 spring-build-conventions:0.0.8.RELEASE 2017-11-27 14:35:15 -06:00
Vedran Pavic
97ad0311e2 Upgrade Spring Data to Kay-SR2
Closes gh-932
2017-11-27 20:07:03 +01:00
Vedran Pavic
702bc37a99 Upgrade Spring Framework to 5.0.2.RELEASE
Closes gh-925
2017-11-27 12:56:09 +01:00
Vedran Pavic
17e56dda18 Polish configuration classes 2017-11-26 12:21:32 +01:00
Vedran Pavic
f5912da089 Optimize HazelcastSessionRepository write operations
This commit introduces several optimizations to write operations in `HazelcastSessionRepository`.

 - when storing a new session, `IMap#set` is now used instead of `IMap#put`
 - when updating an existing session, `IMap#executeOnKey` and a dedicated `EntryProcessor` are used

To make these two changes possible, internal `HazelcastSession` now adds a flag to determine which of the two mentioned write scenarios to use, and also tracks a delta of session attributes in order to optimize updates.

Closes gh-850
2017-11-24 21:06:05 +01:00
Vedran Pavic
bff8ce3c03 Polish samples 2017-11-24 08:21:14 +01:00
Vedran Pavic
a3803e9e1f Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2017-11-23 13:41:48 +01:00
Vedran Pavic
3fcdc9ebce Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-11-23 13:15:47 +01:00
Vedran Pavic
36d157a658 Polish default Redis namespace handling
See gh-919
2017-11-20 09:12:53 +01:00
Vedran Pavic
f28ab07b9a Migrate SpringJUnit4ClassRunner -> SpringRunner 2017-11-20 08:08:00 +01:00
Vedran Pavic
42a6001aae Upgrade Reactor to Bismuth-SR4
Closes gh-929
2017-11-16 20:01:04 +01:00
Vedran Pavic
fc4d2238bc Rename MapReactiveSessionRepository to ReactiveMapSessionRepository
Closes gh-928
2017-11-14 07:26:13 +01:00
Vedran Pavic
36d349f328 Polish contribution
Closes gh-919
2017-11-13 20:59:39 +01:00
Luís Duarte
5f23a41674 Make Redis namespace fully configurable
See gh-919
2017-11-10 22:23:25 +01:00
Vedran Pavic
4c9fbd5b6b Migrate WebFlux sample to Boot
Closes gh-923
2017-11-10 22:11:54 +01:00
Vedran Pavic
f2ba773ec2 Upgrade Spring Boot to 2.0.0.M6
Closes gh-916
2017-11-06 13:01:22 +01:00
Vedran Pavic
647dd7c7bb Add license file 2017-11-02 19:27:25 +01:00
Rob Winch
555223755d Next Development Version 2017-10-30 18:22:30 -05:00
Rob Winch
2e65d89ecc Release 2.0.0.RC1 2017-10-30 18:20:38 -05:00
Rob Winch
f3f18432ee Update to Spring Security 5.0.0.RC1
Fixes gh-904
2017-10-30 18:17:39 -05:00
Vedran Pavic
03f6611e04 Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2017-10-30 09:05:36 +01:00
Vedran Pavic
fff1d83097 Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-10-30 09:01:58 +01:00
Vedran Pavic
91d4a5bfca Add HeaderHttpSessionIdResolver factory methods for commonly used headers
Closes gh-706
2017-10-30 08:07:07 +01:00
Vedran Pavic
34f29cf36c Improve Hazelcast configuration
This commit improves Hazelcast configuration by introducing `@SpringSessionHazelcastInstance` qualifier for explicitly declaring a `HazelcastInstance` to be used by Spring Session. This is in particular useful in scenarios with multiple `HazelcastInstance` beans present in the application context.

 Closes gh-912
2017-10-30 08:03:02 +01:00
Vedran Pavic
7e26897ec2 Add support for configuring Redis session cleanup cron 2017-10-30 01:33:36 -05:00
Vedran Pavic
9ea1fb9af1 Upgrade Spring Data to Kay-SR1
Closes gh-903
2017-10-27 18:31:20 +02:00
Vedran Pavic
2c664d1d9e Move JDBC qualifier annotations to shared package
Closes gh-909
2017-10-27 18:29:49 +02:00
Vedran Pavic
97698fd590 Add support for configuring JDBC session cleanup cron 2017-10-27 14:55:28 +02:00
Vedran Pavic
fe3f40c6f4 Harmonize Redis configurations
This commit improves reactive Redis configuration by adding support for connection factory qualifier and Redis operations resolver annotations.
2017-10-27 13:54:25 +02:00
Vedran Pavic
f8583bb02f Add missing @Override 2017-10-27 09:59:55 +02:00
Vedran Pavic
5df555cd53 Polish 2017-10-27 09:59:51 +02:00
Vedran Pavic
6f05c84aa7 Rename HttpSessionStrategy to HttpSessionIdResolver
This commit harmonizes `HttpSessionStrategy` with Spring Framework's `WebSessionIdResolver` by renaming it to `WebSessionIdResolver`.
2017-10-26 07:29:56 -05:00
Vedran Pavic
cd394bbe10 Align HttpSessionStrategy with WebSessionIdResolver
This commit simplifies `HttpSessionStrategy` API by aligning it with Spring Framework's `WebSessionIdResolver`. As a part of this, support for managing multiple users' sessions has been removed.

Closes gh-275
Closes gh-362
2017-10-26 07:29:56 -05:00
Vedran Pavic
2ecb2e60c0 Improve Redis configuration
This commit improves Redis configuration by introducing `@SpringSessionRedisConnectionFactory` qualifier for explicitly declaring a `RedisConnectionFactory` to be used by Spring Session. This is in particular useful in scenarios with multiple `RedisConnectionFactory` beans present in the application context.

Redis configuration is simplified and no longer registers a Spring Session specific `RedisOperations<Object,Object>` bean with the application context.

Users are however able to obtain `RedisOperations<Object,Object>` instance used by Spring Session using newly introduced `@SpringSessionRedisOperations` annotation.
2017-10-25 07:36:44 -05:00
Vedran Pavic
d04a95ebfb Upgrade Spring Framework to 5.0.1.RELEASE
Closes gh-902
2017-10-24 19:25:07 +02:00
Vedran Pavic
858b52235e Upgrade Reactor to Bismuth-SR3
Closes gh-905
2017-10-24 19:24:29 +02:00
Vedran Pavic
00ede81665 Harmonize naming of reactive components
Closes gh-897
2017-10-24 07:36:25 +02:00
Rob Winch
6cfa975b29 Move Redis Reactive Configuration
Renaming the package to better align with WebSession pacakge structure

Fixes gh-901
2017-10-22 22:37:21 -05:00
Rob Winch
8b9d421ad6 Tangles in RedisOperationsSessionRepository
Issue: gh-900
2017-10-22 22:33:36 -05:00
Rob Winch
df7ab9d99e Package tangles for Reactive Configuration
Fixes gh-900
2017-10-22 22:33:29 -05:00
Vedran Pavic
7d61c5496a Fix deprecation warnings 2017-10-20 21:18:31 +02:00
Vedran Pavic
3492bc01d2 Upgrade Spring Boot to 2.0.0.M5
Closes gh-892
2017-10-12 11:35:46 +02:00
Vedran Pavic
e08ac357dd Upgrade spring-build-conventions to 0.0.5.RELEASE 2017-10-10 22:06:01 +02:00
Rob Winch
1c29c7f14f Update to 5.0.0.BUILD-SNAPSHOT 2017-10-09 17:10:11 -05:00
Rob Winch
33fbaa03a8 Release 2.0.0.M5 2017-10-09 16:54:31 -05:00
Rob Winch
88b26f2cfe Update to Spring Security 5.0.0.M5
Fixes gh-891
2017-10-09 16:53:41 -05:00
Vedran Pavic
3f670050ef Update integration tests
This commit updates versions of RDBMS and Redis Docker images used in
integration tests.

Closes gh-894
2017-10-09 08:09:52 +02:00
Vedran Pavic
e3b61d25bb Improve JDBC configuration
This commit improves JDBC configuration by introducing `@SpringSessionDataSource` qualifier for explicitly declaring a `DataSource` to be used by Spring Session. This is in particular useful in scenarios with multiple `DataSource` beans present in the application context.

As a consequence, JDBC configuration is simplified and no longer registers a Spring Session specific `JdbcTemplate` bean.

Closes gh-863
2017-10-06 19:12:55 +02:00
Vedran Pavic
19b8effa41 Add Redis implementation of ReactorSessionRepository
Closes gh-816
2017-10-06 18:45:42 +02:00
Vedran Pavic
9f5f7540d2 Fix Users sample app navbar
Closes gh-885
2017-10-02 22:31:55 +02:00
Vedran Pavic
eb8c22939c Upgrade Gradle to 4.2.1 2017-10-02 21:20:14 +02:00
Vedran Pavic
45cfa1e9a4 Upgrade spring-build-conventions to 0.0.4.RELEASE 2017-10-02 20:53:06 +02:00
Vedran Pavic
99221e0948 Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-10-02 19:01:47 +02:00
Vedran Pavic
41cf2ef152 Update documentation to reflect preference for Lettuce
See gh-886
2017-10-02 19:00:23 +02:00
Vedran Pavic
c51bce4777 Use Lettuce driver for integration tests
Closes gh-886
2017-09-28 16:17:55 +02:00
Vedran Pavic
b6f1184c4c Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-09-28 15:51:32 +02:00
Vedran Pavic
c69a8b8762 Improve JDBC data store schema scripts
Closes gh-884
2017-09-27 12:18:25 +02:00
Vedran Pavic
99fb17a66b Adapt to Spring WebSession API changes 2017-09-27 11:52:04 +02:00
Vedran Pavic
937b2fcbf1 Upgrade Gradle to 4.2 2017-09-25 09:06:06 +02:00
Vedran Pavic
9c5a7e9156 Upgrade Spring Boot to 2.0.0.M4
Closes gh-877
2017-09-15 22:13:23 +02:00
Vedran Pavic
4deccd3ad0 Upgrade Gradle to 4.1 2017-09-15 21:24:48 +02:00
Vedran Pavic
da058e9510 Upgrade dependencies to latest snapshots
- Reactor Bismuth-BUILD-SNAPSHOT
- Spring Framework 5.0.0.BUILD-SNAPSHOT
- Spring Data Kay-BUILD-SNAPSHOT
- Spring Security 5.0.0.BUILD-SNAPSHOT
2017-09-14 07:18:24 +02:00
Vedran Pavic
d28ca4658b Next development version 2017-09-14 07:16:49 +02:00
Rob Winch
c14fdb283d Release 2.0.0.M4 2017-09-13 18:04:06 -05:00
Rob Winch
ee1ff3ed3b Update Spring Security 5.0.0.M4 2017-09-13 18:03:33 -05:00
Vedran Pavic
eb7bcc5eeb Harmonize ReactorSessionRepository API
This commit renames the `ReactorSessionRepository#delete` to `deleteById` in order to make API consistent with `SessionRepository`.
2017-09-12 23:00:16 +02:00
Vedran Pavic
188e5ba4e0 Optimize JDBC session cleanup SQL statement
This commit improves session cleanup handling in  `JdbcOperationsSessionRepository#cleanUpExpiredSessions` by optimizing the used SQL statement. This is done by calculating the session expiry time when persisting the session, which in turn allows the cleanup SQL statement to be more index-friendly.

Closes gh-847
2017-09-12 15:41:10 -05:00
Vedran Pavic
1e46630467 Remove MapReactorSessionRepository default constructor
This commit removes the default `MapReactorSessionRepository` so that the users are required to explicitly supply the `Map` used to store the sessions.
2017-09-12 15:22:29 -05:00
Vedran Pavic
b72c600884 Upgrade dependencies to current milestones
- Reactor Bismuth-M4
- Spring Framework 5.0.0.RC4
- Spring Data Kay-RC3
- Lettuce 5.0.0.RC2
2017-09-12 07:54:14 +02:00
Vedran Pavic
274aec1691 Fix Boot based samples 2017-09-11 13:57:38 +02:00
Rob Winch
52ea98b4ce SpringWebSessionConfigurationTests close ApplicationContext 2017-09-07 20:12:45 -05:00
Rob Winch
5c294ae1d2 Polish 2017-09-07 20:12:45 -05:00
Greg Turnquist
1752928d96 Configure WebSessionManager's WebSessionIdResolver by bean definition
Allow a WebSessionIdResolver registered as a Spring bean to be wired into the WebSessionManager.
2017-09-07 20:12:45 -05:00
Vedran Pavic
0cdee25405 Remove MapSessionRepository default constructor
This commit removes the default `MapSessionRepository` constructor so that the users are required to explicitly supply the `Map` used to store the sessions.
2017-09-07 19:58:22 -05:00
Rob Winch
4a9f1700d5 Polish
Reorder methods
2017-09-06 15:12:13 -05:00
Rob Winch
36ab358d24 Remove SpringSessionWebSessionManager
Spring's DefaultWebSessionManager now supports all the functionality
that is needed for Spring Session, so we only need to implement
WebSessionStore
2017-09-06 14:50:48 -05:00
Vedran Pavic
8e3371aed9 Allow easier customization of cookie max age logic 2017-08-24 16:00:59 -05:00
mikemassa84
2161f966de Update grails3.adoc
Add a note about spring-session and grails flash scope, with link to stackoverflow answer.
2017-08-24 14:37:32 -05:00
Vedran Pavic
63b67a501d Update guides for Redis based samples 2017-08-24 14:17:10 -05:00
Vedran Pavic
2b0431eae4 Use TestContainers for Gretty integration tests 2017-08-24 14:17:10 -05:00
Vedran Pavic
04ec086014 Use TestContainers for integration tests 2017-08-24 14:17:10 -05:00
Rob Winch
5697f49a71 Config->HelloWebfluxSessionConfig
Use a more meaningful name
2017-08-24 13:54:21 -05:00
Rob Winch
dfce66383f webflux sample uses @EnableSpringWebSession
Issue gh-861
2017-08-24 13:52:52 -05:00
Rob Winch
a83e59bf52 Polish
Fix checkstyle

Issue gh-861
2017-08-24 13:52:24 -05:00
Greg Turnquist
8b233e84ef Create @EnableSpringWebSession annotation. 2017-08-24 13:41:42 -05:00
Greg Turnquist
84e7fbace1 Fix MapReactorSessionRepository's delete(). 2017-08-24 13:40:57 -05:00
Rob Winch
f455df3333 Add WebFlux sample
Fixesh gh-857
2017-08-18 16:28:46 -05:00
Rob Winch
a7bb9d3b31 SpringSessionWebSessionManager writes on commit
when the ServerHttpResonse is commited the cookie and the session are written

Fixes gh-856
2017-08-18 16:28:46 -05:00
Kanjie Lu
5f0e4c3b85 fix typo
change "they key" to  "the key"
2017-08-05 20:39:08 -05:00
Vedran Pavic
23c6c7cf31 Upgrade Spring Boot to 2.0.0.M3
Closes gh-841
2017-07-27 14:08:48 +02:00
Vedran Pavic
c8c5fae678 Polish build 2017-07-26 08:49:45 +02:00
Vedran Pavic
f4a58622e4 Upgrade dependencies to latest snapshots
- Reactor Bismuth-BUILD-SNAPSHOT
- Spring Framework 5.0.0.BUILD-SNAPSHOT
- Spring Data Kay-BUILD-SNAPSHOT
- Spring Security 5.0.0.BUILD-SNAPSHOT
2017-07-26 08:47:23 +02:00
Joe Grandja
5384764021 Next development version 2017-07-25 10:12:45 -04:00
Joe Grandja
56033a9b68 Release version 2.0.0.M3 2017-07-25 09:31:12 -04:00
Vedran Pavic
99a2b079ac Upgrade dependencies to current milestones
- Spring Framework 5.0.0.RC3
- Spring Data Kay-RC1
- Spring Security 5.0.0.M3
2017-07-25 10:11:26 +02:00
Vedran Pavic
9120151692 Polish "Add WebFlux Support"
Closes gh-683
2017-07-21 15:31:45 +02:00
Rob Winch
5abbe66b1d Add WebFlux Support 2017-07-21 15:30:19 +02:00
Rob Winch
f00c196430 Update Dependencies to Prepare Release 2017-07-20 22:24:35 -05:00
Rob Winch
be2604ca69 Add Session.changeSessionId 2017-07-20 16:31:38 -05:00
Rob Winch
2aa71ffb6d Update to lettuce 5.0.0.RC1 2017-07-20 09:54:53 -05:00
Rob Winch
8bdcba6e50 Spring Session 2.0.0.M2 w/ Boot Samples
This is necessary because the current version of Spring Session is not
compatible with Boot 2.0.0.M2 and we cannot release against the SNAPSHOTs

Issue gh-833
2017-07-18 20:09:21 -05:00
Mark Paluch
8dd1a10f1b Adapt to changes in Spring Data Redis API 2017-07-18 16:19:05 +02:00
Vedran Pavic
1d247aa96f Align Checkstyle config with spring-build-conventions 2017-07-12 08:24:09 +02:00
Vedran Pavic
c00d6a7bf2 Apply correct convention plugin for modules 2017-07-10 09:10:33 +02:00
Vedran Pavic
c0df3bf28b Fix deprecation warnings 2017-07-10 07:28:09 +02:00
Vedran Pavic
1b8c9838a4 Fix unchecked operations build warning 2017-07-10 07:27:27 +02:00
Vedran Pavic
8a1b454121 Fix javadoc build warning 2017-07-10 07:26:35 +02:00
Vedran Pavic
ef69c8169a Polish dependencies
This commit removes needless dependency exclusions.

See gh-824
2017-07-10 07:20:35 +02:00
Rob Winch
40b3d07224 Revert "Add --debug to Jenkinsfile"
This reverts commit 9c4e20f074.
2017-07-07 11:23:54 -05:00
Rob Winch
8c726f2215 Use Gradle 3.5.1
This works around a bug in 4.0 where Gradle is hanging when trying to
resolve a configuration.
2017-07-07 11:17:32 -05:00
Rob Winch
c2a86a27ce Travis skip install 2017-07-07 10:16:13 -05:00
Rob Winch
6a08ef6f97 Polish travis build 2017-07-07 10:03:54 -05:00
Rob Winch
9c4e20f074 Add --debug to Jenkinsfile
Try and troubleshoot why the build is hanging
2017-07-07 09:42:24 -05:00
Vedran Pavic
5845a9c46a Improve dependency management
This commit improves dependency management with the following changes:

 - `spring-session-core`: move `javax.servlet-api` from `provided` to `optional` configuration due to introduction of reactive support
 - `spring-session-data-redis`: remove Redis driver from `compile` configuration
 - Boot samples: delegate Redis driver choice to `spring-boot-starter-data-redis`
 - polish `test` configuration dependencies
2017-07-06 17:05:01 -05:00
Rob Winch
7c6693a268 Remove Sonar to see if it fixes build 2017-07-06 08:49:21 -05:00
Vedran Pavic
05a3f59813 Simplify Hazelcast sample 2017-07-05 16:00:28 -05:00
Vedran Pavic
47a7a35aa4 Remove use of Assert#notNull from core components
Fixes gh-820
2017-07-05 17:04:03 +02:00
Vedran Pavic
04b4fe3e3b Fix Checkstyle violations 2017-07-01 00:04:46 +02:00
Rob Winch
36bb65e4b5 Add default methods to Session
Fixes gh-819
2017-06-30 10:25:14 -05:00
Rob Winch
8ef36e4f3e Session Optional<T> getAttribute -> T getAttribute
Issue gh-819
2017-06-30 10:24:59 -05:00
Rob Winch
ab3e280993 Update to latest SNAPSHOTs 2017-06-29 22:20:26 -05:00
Rob Winch
30562b5749 Use Spring IO Cairo-BUILD-SNAPSHOT 2017-06-27 16:37:13 -05:00
Rob Winch
d42a7b65ea Add MapReactorSessionRepository
Fixes gh-815
2017-06-27 16:32:51 -05:00
Rob Winch
db9807d12b Add ReactorSessionRepository
Fixes gh-814
2017-06-27 16:32:51 -05:00
Rob Winch
db09fa8168 Use SPRING_SESSION_TEAM_EMAILS 2017-06-26 10:52:17 -05:00
Vedran Pavic
031541bc05 Fix Checkstyle violations 2017-06-26 08:45:20 +02:00
Vedran Pavic
084e3428fb Move SessionEventRegistry back to integration-test sources
Fixes gh-810
2017-06-23 19:03:46 +02:00
Vedran Pavic
b321ff02f0 Revert "SessionRepository.save returns S"
See gh-809
2017-06-23 18:48:30 +02:00
Rob Winch
c6c6beb40c Session.delete -> deleteById
Fixes gh-809
2017-06-22 21:30:14 -05:00
Rob Winch
0127ef9f9b SessionRepository.getSession(String) -> findById(String)
Issue gh-809
2017-06-22 21:29:34 -05:00
Rob Winch
cd8686ae9c SessionRepository.save returns S
Issue gh-809
2017-06-22 21:27:25 -05:00
Rob Winch
233d179bfa Revert "Allow Publishing When Spring IO Fails"
This reverts commit fca411996a.
2017-06-22 13:20:23 -05:00
Rob Winch
4e8ae8d9d4 Revert "Deploy Without Checks for Spring IO"
This reverts commit 8c6810c6dd.
2017-06-22 13:20:21 -05:00
Rob Winch
8c6810c6dd Deploy Without Checks for Spring IO 2017-06-22 13:18:01 -05:00
Rob Winch
fca411996a Allow Publishing When Spring IO Fails
This is necessary to solve the problem of chicken and the Egg. See
https://github.com/spring-io/platform/issues/622#issuecomment-310452646
2017-06-22 13:00:12 -05:00
Rob Winch
79b8296e1c Work Around spring-projects/spring-boot#9573
Issue gh-806
2017-06-22 09:04:23 -05:00
Rob Winch
043cb42149 Extract spring-session-jdbc
Issue gh-806
2017-06-22 09:04:09 -05:00
Rob Winch
c28f047eb5 Extract spring-session-data-hazelcast
Issue gh-806
2017-06-22 09:03:50 -05:00
Rob Winch
972cf66d7e Extract spring-session-data-redis
Issue gh-806
2017-06-22 09:03:25 -05:00
Rob Winch
f1319483ee Move spring-session to spring-session-core
Issue gh-806
2017-06-22 09:02:44 -05:00
Rob Winch
6ad5006280 Update to Gradle 4.0 2017-06-16 13:40:43 -05:00
Vedran Pavic
f7e07b7f6b Improve Session API to use Java 8 2017-06-16 11:44:19 -05:00
Vedran Pavic
4cf26d9c36 Move ExpiringSession API into Session 2017-06-16 11:44:19 -05:00
Vedran Pavic
a848df1235 Replace explicit type arguments with diamond operator 2017-06-16 11:44:19 -05:00
Rob Winch
f8292ba512 Update to Spring Boot 2.0.0.M2
Fixes gh-801
2017-06-16 10:29:30 -05:00
Rob Winch
21bcc6e8d7 Next Development Version 2017-06-15 20:35:20 -05:00
Rob Winch
905a77a3a8 Release 2.0.0.M2 2017-06-15 20:08:29 -05:00
Rob Winch
295f9f78c3 Update to latest Releases
- Spring Framework 5.0.0.RC2
- Spring Security 5.0.0.M2
- Spring Data Kay-M4
2017-06-15 20:08:15 -05:00
Rob Winch
04ecc82d09 Polish Artifactory Publish 2017-06-15 11:40:04 -05:00
Rob Winch
2ddd9e58a3 Update to spring-build-conventions 0.0.2.RELEASE 2017-06-15 11:39:21 -05:00
Vedran Pavic
3c52298c47 Add support for configuring default CookieSerializer using SessionCookieConfig
Fixes gh-87
2017-06-13 22:01:15 +02:00
Rob Winch
7b385c7d33 Exclude check on artifactoryPublish 2017-06-08 16:54:16 -05:00
Rob Winch
87d51c54c9 Add Artifactory Deploy 2017-06-08 16:42:55 -05:00
Rob Winch
210e8eebc5 Update to spring-build-conventions 0.0.2.BUILD-SNAPSHOT 2017-06-08 16:38:39 -05:00
Vedran Pavic
7d52c87173 Improve Spring Boot based samples
Starting with 2.0.0.M1, Boot provides auto-config support for Lettuce.

See gh-790
2017-06-07 22:48:46 +02:00
Vedran Pavic
e3c6fb67f2 Replace StringBuffer usages with StringBuilder
Fixes gh-718
2017-05-30 22:40:28 +02:00
Vedran Pavic
79f187ddd6 Upgrade samples to Boot 2.0.0.M1
Fixes gh-782
2017-05-30 22:37:36 +02:00
Vedran Pavic
22f4b0bc9d Update minimum requirements 2017-05-25 23:59:49 +02:00
Vedran Pavic
c5ea626d03 Improve layout of community extensions doc section
Fixes gh-786
2017-05-17 11:49:49 +02:00
Vedran Pavic
d067cd1e66 Remove obsolete .gradle files
Fixes gh-785
2017-05-17 11:45:56 +02:00
Rob Winch
76a6be572a Next development version 2017-05-10 13:47:17 -05:00
Spring Buildmaster
78db900303 Release version 2.0.0.M2 2017-05-10 07:08:41 +00:00
Rob Winch
7f9a9c4185 Update to lettuce-core 5.0.0.M2 2017-05-10 01:44:44 -05:00
Rob Winch
83c67d3e11 Update to Spring Security 5.0.0.M1 2017-05-10 01:44:18 -05:00
Rob Winch
4f3324bac4 Update to spring-build-conventions 0.0.1.RELEASE 2017-05-10 01:44:04 -05:00
Rob Winch
23a28f790a Disable OSSRH Deploy
For the release disable OSSRH Deploy since we will release only
to Artifactory for the milestone. We cannot deploy to OSSRH because
we have RC dependencies that are not in Maven Central.
2017-05-10 00:46:45 -05:00
Vedran Pavic
dd4983f33e Improve samples logging 2017-05-10 00:24:57 -05:00
Vedran Pavic
e9e5d8eda6 Parameterize SpringSessionBackedSessionRegistry 2017-05-10 00:24:16 -05:00
Rob Winch
a745d471ad Update to Spring Data Kay M3 2017-05-09 09:36:54 -05:00
Rob Winch
df267774da Use dependency-management.gradle 2017-05-09 02:52:53 -05:00
Vedran Pavic
2b2f385d5f Use spring-jcl instead of commons-logging 2017-05-09 02:50:27 -05:00
Vedran Pavic
86e892c806 Improve Spring Boot based samples
Fixes gh-693
2017-05-04 18:56:12 +02:00
Vedran Pavic
448133494f Replace anonymous types with lambda 2017-05-03 10:06:56 -05:00
Vedran Pavic
e0fc9e92ba Simplify Map.get and conditional calls 2017-05-03 10:06:56 -05:00
Vedran Pavic
5b4d0c40d8 Replace explicit type arguments with diamond operator 2017-05-03 10:06:56 -05:00
Rob Winch
78ea101a43 Add logback.xml
This will ensure the logs are not too large which will improve performance
and ensure that the build works on Travis which caps the log file size.
2017-05-03 10:06:44 -05:00
Vedran Pavic
63097e9d82 Fix broken documentation links 2017-04-28 20:05:37 +02:00
Vedran Pavic
2ebbe762f0 Downgrade Spring Boot to 1.5.3.RELEASE
Fixes gh-710
2017-04-28 17:27:49 +02:00
Rob Winch
63b836b212 Remove Mongo
Fixes gh-768
2017-04-26 22:19:10 -05:00
Rob Winch
02da23a2a0 Remove GemFire
Fixes gh-768
2017-04-26 22:19:10 -05:00
Rob Winch
b254c7c6b9 Add Jenkinsfile
Fixes gh-770
2017-04-26 22:19:10 -05:00
Rob Winch
89adc13201 Use Optional 2017-04-26 22:19:10 -05:00
Rob Winch
e6e752aea5 Servlet 3.1 compatible 2017-04-26 22:19:10 -05:00
Rob Winch
d590ca58e4 Standardize Build
Fixes gh-769
2017-04-26 22:11:41 -05:00
Rob Winch
e23aaeca5f Fix .gitignore 2017-04-26 22:11:41 -05:00
Vedran Pavic
0312c31a42 Use explicit constraints in JDBC schema scripts
Fixes gh-750
2017-04-26 23:31:18 +02:00
Vedran Pavic
815cbf4ee8 Update DefaultCookieSerializer to use base64 by default
Fixes gh-736
2017-04-26 22:49:47 +02:00
Eddú Meléndez
6327d36ce9 Use Base64 implementation provided by Java 8
Fixes gh-735
2017-04-26 22:43:44 +02:00
Rob Winch
707b8bb062 Revert "Workaround Lettuce Bug"
This reverts commit adbff45a23.

Fixes gh-759
2017-04-26 11:30:28 -05:00
Rob Winch
adbff45a23 Workaround Lettuce Bug
Issue gh-759
2017-04-26 08:57:22 -05:00
Rob Winch
f30cb7a1e6 SpringSessionRememberMeServices rm SecurityContext attribute
SpringSessionRememberMeServices use to invalidate the session which would
cause Spring Security's saved request to be lost.

Now SpringSessionRememberMeServices deletes the SecurityContext from the
HttpSession instead.

Fixes gh-752
2017-04-26 08:57:22 -05:00
John Blum
432eb84a94 Restore proper behavior of HttpSession created events in GemFire support when client Region is a PROXY in the client/server topology
Fixes gh-757
2017-04-25 20:03:11 -07:00
John Blum
bd31710117 Upgrade to Jackson 2.9.0.pr2
Jackson 2.9.0.pr2 is required by the latest Spring Data Redis Kay
2017-04-22 01:20:46 -07:00
John Blum
e67afefcd8 Fix Java 8 ambiguous compiler errors 2017-04-22 01:04:19 -07:00
John Blum
327323da38 Upgrade to io.spring.dependency-management Gradle plugin 1.0.2.RELEASE
Apply io.spring.dependency-management Gradle plugin to all Boot samples to properly resolve Boot dependencies with implicit versioning
2017-04-21 23:21:39 -07:00
John Blum
83e5d6f2a7 Upgrade to Java 8 source and target baseline 2017-04-21 23:19:01 -07:00
John Blum
887f024551 Fix test failure
Polish for #gh-755
2017-04-21 23:16:45 -07:00
John Blum
8dd6aa38ed Upgrade to io.lettuce:lettuce-core:5.0.0.BUILD-SNAPSHOT 2017-04-21 22:16:05 -07:00
John Blum
6f4025eacb Simply GemFire configuration in docs GemFire Indexing integration tests 2017-04-21 22:04:50 -07:00
John Blum
3cc53fae2c Upgrade to Gradle 3.5 2017-04-21 21:00:24 -07:00
John Blum
25ded686ac Improve GemFire SessionRepository, Session copy logic to avoid issues with delta propagation on updates
Fixes #gh-755

(cherry picked from commit dcc0c07981)
Signed-off-by: John Blum <jblum@pivotal.io>
2017-04-21 20:58:44 -07:00
Vedran Pavic
9b30726805 Fix typo in .gitignore 2017-03-23 19:14:16 +01:00
Vedran Pavic
aeb182712c Remove logging for "Skip invoking on" response committed
Fixes gh-734
2017-03-20 20:19:49 +01:00
Sebastian Laskawiec
18ccee051f Add link to Infinispan for Spring Session documentation
Fixes gh-745
2017-03-20 19:53:13 +01:00
Vedran Pavic
3f239b4956 Fix Spring Boot deprecation warnings 2017-03-05 12:13:11 +01:00
Vedran Pavic
dc3b6ba6f1 Fix Mockito deprecation warnings 2017-03-05 12:13:03 +01:00
Rob Winch
ddf9ef66c1 Fix selenium versions
Spring Platform Updated versions. Fix selenium dependencies to work
with them.
2017-03-03 10:58:27 -06:00
Rob Winch
b65423f296 FindByUsernameTests check if driver is null
Fixes gh-740
2017-03-03 10:53:12 -06:00
John Blum
b3706addbb Introduce more reliable coordination between a GemFire client/server during integration tests.
Fixes gh-672
2017-01-31 12:47:28 -08:00
John Blum
5c6565bd9c Fix compilation error caused by improper use of Assert.notNull(..)
Fixes gh-724
2017-01-30 16:10:34 -08:00
John Blum
3e24393e9a Fixes GemFire client/server integratione tests issue when setting JAVA_TOOL_OPTIONS env var
Fixes gh-669
2017-01-30 15:20:06 -08:00
John Blum
0e10b7763c Remove 'thymeleaf-extras-conditionalcomments' dependency
Fixes gh-721
2017-01-25 22:27:51 -08:00
Vedran Pavic
012f121c48 Prevent NPE inMongoOperationsSessionRepository when creating session if max inactive interval is undefined
Fixes gh-716
2017-01-21 22:58:06 +01:00
Vedran Pavic
c0cc15679c Improve Hazelcast support documentation
Fixes gh-680
2017-01-17 21:36:40 +01:00
Rob Winch
43d83f6398 Polish 2017-01-16 16:05:36 -06:00
Vedran Pavic
862659b9b7 Restructure samples 2017-01-14 10:29:30 +01:00
John Blum
536156a4ec Consistentely apply Spring Data BOM across Spring Session modules
Fixes gh-709
2017-01-13 17:54:44 -08:00
John Blum
aa3536a71a Set Spring dependencies to build snapshots
Sets Spring Framework to 5.0.0.BUILD-SNAPSHOT

Sets Spring Data to 2.0.0.BUILD-SNAPSHOT

Sets Spring Data Release Train to Kay-BUILD-SNAPSHOT

Sets Spring Security to 4.2.2.BUILD-SNAPSHOT

Fixes gh-709
2017-01-13 16:05:12 -08:00
John Blum
dd23c96c1a Set Spring Boot version to 2.0.0.BUILD-SNAPSHOT
Fixes gh-709
2017-01-13 16:01:50 -08:00
Vedran Pavic
41fbc90ec2 Fix Gradle deprecation warnings 2017-01-12 21:58:03 +01:00
Vedran Pavic
3cc3784313 Remove deprecations
This commit removes `SessionMessageListener` and `CookieHttpSessionStrategy#setCookieName` which both were deprecated since `1.1.0`, and `SessionEntryListener` which was deprecated since `1.3.0`.

Fixes gh-675
2017-01-12 21:20:52 +01:00
Eddú Meléndez
489cf01812 Polish samples
Fixes gh-698
2017-01-12 21:15:50 +01:00
Vedran Pavic
94fc80a8f0 Use capitalized words for HeaderHttpSessionStrategy default header name
Fixes gh-173
2017-01-12 18:54:53 +01:00
Rob Winch
3ad0028785 Use relative xpath
Issue gh-702
2017-01-11 15:54:03 -06:00
Rob Winch
801f88d793 Remove lombok from build.gradle
Issue gh-702
2017-01-11 15:40:02 -06:00
Rob Winch
0d6b62b7a9 Remove Lombok from httpsession-gemfire-boot
Fixes gh-702
2017-01-11 15:29:21 -06:00
Rob Winch
00d5d76833 Fix Buildship import with eclipse.jdt.javaRuntimeName
See https://discuss.gradle.org/t/building-with-newer-jdks/21102
2017-01-11 14:38:31 -06:00
Rob Winch
85a1b43242 Update to Gradle 3.3 2017-01-11 14:38:14 -06:00
John Blum
800d52279f Disable spring3Test check 2017-01-10 02:02:59 -08:00
John Blum
61a6344ffa Upgrade to Spring Data Kay
Dependency updates supporting Kay:

Upgrade Spring Framework to 5.0.0.M3

Upgrade Spring Boot to 1.5.0.RC1

Upgrade Jackson to 2.7.6

Upgrade Jedis to 2.9.0

Upgrade Lettuce to 5.0.0.Beta1

Upgrade Mockito to 2.5.4

Fixes #gh-677
2017-01-10 01:52:21 -08:00
Rob Winch
c182e90a1a httpsession-xml fixes 2017-01-09 21:49:48 -08:00
Rob Winch
7dc3e12e07 Polish httpsession-redis-json 2017-01-09 21:49:38 -08:00
Rob Winch
ce5e44233e Polish httpsession-jdbc-xml 2017-01-09 21:49:30 -08:00
Rob Winch
22c416e32b Polish jdbc-boot 2017-01-09 21:49:23 -08:00
Rob Winch
ff72bf1234 Additional boot polish 2017-01-09 21:49:14 -08:00
Rob Winch
a45199059b Polish httpsession-jdbc 2017-01-09 21:49:08 -08:00
Rob Winch
37045e337c Polish httpsession 2017-01-09 21:49:00 -08:00
Rob Winch
41e3f91b75 Polish hazelcast-spring 2017-01-09 21:48:50 -08:00
Rob Winch
efeed5e2cf Polish hazelcast 2017-01-09 21:48:42 -08:00
Rob Winch
1952a4550f Polish findbyusername 2017-01-09 21:48:34 -08:00
Rob Winch
9efb5b59e5 Polish custom-cookies 2017-01-09 21:48:23 -08:00
Rob Winch
4b196744f2 Polish 2017-01-09 21:48:15 -08:00
Pool Dolorier
8e7c736a0a Move groovy test to java 2017-01-09 21:48:02 -08:00
Eddú Meléndez
1a318b89d9 Convert groovy tests to java 2017-01-09 21:47:57 -08:00
Rob Winch
f98697416e spring-boot-starter-data-redis 2017-01-09 21:47:47 -08:00
Rob Winch
d66fa56513 Spring IO defaults to Cairo 2017-01-09 21:47:41 -08:00
Rob Winch
89c91c19d8 Fix spring platform Brussels
* Update to dependency-management-1.0.0.RC2
* Update to spring-io-plugin 0.0.6.RELEASE
* Update to Boot 1.5 (which requires newer dependency management plugin)
2017-01-05 14:59:15 -06:00
Rob Winch
5e294b805f Fix Formatter 2017-01-05 14:05:07 -06:00
Rob Winch
a7b5f86bcd Start 2.0.0.BUILD-SNAPSHOT 2016-12-19 13:39:02 -06:00
Rob Winch
9c236fa256 Start 1.4.x 2016-12-19 13:38:08 -06:00
Rob Winch
304e32eef5 Fix Typo 2016-12-19 13:35:07 -06:00
Spring Buildmaster
288c622012 Next development version 2016-12-15 22:30:26 +00:00
Spring Buildmaster
3827ae1e72 Release version 1.3.0.RELEASE 2016-12-15 22:30:19 +00:00
Rob Winch
9067f8235d Add What's New in 1.3
Fixes gh-627
2016-12-15 15:56:16 -06:00
Rob Winch
19928e6b7f Add MongoSession.isExpired interval < 0 Test
Issue gh-629
2016-12-14 08:52:43 -06:00
Joe Atkins
1df1a76069 Prevent expiration on RedisSession interval < 0
Since a negative maxInactiveInterval is supposed to disable
expiration, if it is negative, use persist on the session's
spring:session:session and spring:session:expires keys to
prevent the expiration of the RedisSession.

Issue gh-629
2016-12-14 08:51:19 -06:00
Joe Atkins
17e397212d Fix MongoExpiringSession.isExpired interval < 0
Verify that interval is non-negative, as negative values for
maxInactiveInterval disables expiration.

Issue gh-629
2016-12-14 08:49:26 -06:00
Vedran Pavic
39503a21a7 Refactor JdbcOperationsSessionRepository session clean up query to prevent overflow
Fixes gh-679
2016-12-02 21:29:47 +01:00
Gabor Csizmadia
ebbc10b2b4 Fix misleading comment about SessionCreatedEvent
Fixes gh-678
2016-11-28 22:54:06 +01:00
John Blum
9a51cb9ca7 Minor changes to improve the timing between Spring Boot-based GemFire client and server connections
Fixes gh-672
2016-11-22 22:48:37 -08:00
Spring Buildmaster
5e0ee5077a Next development version 2016-11-23 04:15:45 +00:00
Spring Buildmaster
526c6ee012 Release version 1.3.0.RC1 2016-11-23 04:15:39 +00:00
Eddú Meléndez
ff4045acbd Support placeholder resolution for collectionName in EnableMongoHttpSession 2016-11-22 21:48:54 -06:00
Eddú Meléndez
2d359986d3 Support placeholder resolution for redisNamespace in EnableRedisHttpSession annotation
Fixes gh-381
2016-11-22 21:45:55 -06:00
Eddú Meléndez
6424910c83 Support placeholder resolution for tableName in EnableJdbcHttpSession
Fixes gh-512
2016-11-22 21:22:19 -06:00
Rob Winch
49e3a1c7cd Polish SpringSessionRememberMeServices
* Move to ~.security.web.authentication package
* Add documentation
* Use getBeanNamesForType to avoid eager bean initialization
* Remove rememberMeCookieMaxAge because it must be Integer.MAX_VALUE since
  cookie is only written at the creation of the session
* Change from parameter to rememberMeParameterName

Issue gh-189
2016-11-22 20:46:42 -06:00
Rob Winch
3b8258f233 Polish Checkstyle 2016-11-22 16:20:42 -06:00
Vedran Pavic
a2b30eb54b Add Spring Security's RememberMeServices implementation backed by Spring Session 2016-11-19 15:11:04 +01:00
marcoblos
4c2581d432 Polish RedisSession
Maintain standard using  prefix inside the RedisSession to mathods
call. Generalizing  and  methods.

Fixes gh-638
2016-11-17 10:33:18 -06:00
Rob Winch
25aec99357 Polish CURRENT_SESSION_ATTR
* Remove unnecessary comment
* Make relative to SESSION_REPOSITORY_ATTR

Issue gh-654
2016-11-16 14:55:10 -06:00
Alex Panchenko
1c9dfa6638 static field for constant in SessionRepositoryFilter
Fixes gh-654
2016-11-16 14:54:57 -06:00
Rob Winch
466e2cf102 Fix docs security-config.xml 2016-11-16 14:31:45 -06:00
Vedran Pavic
eb0f292c20 Fix docs module Sonar build 2016-11-16 17:59:36 +01:00
Vedran Pavic
43fda301e2 Fix Sonar build 2016-11-15 23:07:00 +01:00
Vedran Pavic
4a06b38c5f Polish contribution (#516)
Issue gh-516
2016-11-15 21:20:22 +01:00
Aleksandar Stojsavljevic
6a78101db5 Optimize save operation in HazelcastSessionRepository (#516)
This commit improves saving of sessions to only execute save
operation if something has been changed
(e.g. session.setAttribute(String, Object) was called).
Further, configurable flush mode that specifies when to write
to the backing Hazelcast instance is introduced. It can be
'on save' (default) or 'immediate'.

Fixes gh-516, fixes gh-641
2016-11-15 21:16:33 +01:00
Rob Winch
b5ea6c752d Update to Sonar 2.2 to work with Gradle 3.1 2016-11-14 18:45:56 -06:00
Rob Winch
46633274d5 Add eclipseConfiguration task 2016-11-14 16:54:16 -06:00
Rob Winch
038287b2cc Use $mockitoVersion 2016-11-14 16:54:16 -06:00
Rob Winch
32c053271c Add Tests for Closing RedisConnection
Issue gh-656
2016-11-14 16:54:16 -06:00
Alex Panchenko
802e0e714b close RedisConnection in EnableRedisKeyspaceNotificationsInitializer
Issue gh-626
2016-11-14 13:49:41 -06:00
Vedran Pavic
6263f6e927 Upgrade Gradle to 3.1
Fixes gh-668
2016-11-14 18:48:25 +01:00
Vedran Pavic
5671037c39 Delete refactor from findbyusername doc
Fixes gh-671
2016-11-14 18:33:49 +01:00
Vedran Pavic
3d44467275 Fix broken link in find by username guide
Fixes gh-670
2016-11-14 18:17:10 +01:00
Eddú Meléndez
94221c70a9 Clear warnings from spring-boot gradle plugin 2016-11-11 15:27:55 -06:00
John Blum
dd3a571494 Avoid premature destruction of the GemFire Pool used by the client Sessions Region.
Fixes gh-665
2016-11-10 18:09:39 -08:00
Mark Paluch
e4fe53abf8 Update Samples and Guides to use Lettuce
Favor lettuce because of multiplexing and improved scalability.
Using lettuce requires a fixed number of connections hence using
lettuce improves application scalability.

Fixes gh-652
2016-11-10 16:56:19 -06:00
Rob Winch
7b65d7930b Fix Checkstyle
Issue gh-657
2016-11-10 16:30:59 -06:00
Rob Winch
0f8326516b Add Tests
Issue gh-657
2016-11-10 16:13:18 -06:00
Alex Panchenko
2aec28289e Skip redis getConnection() if ConfigureRedisAction.NO_OP
Issue: gh-653
2016-11-10 16:01:04 -06:00
Rob Winch
af7a5a208f Update to Spring Boot 1.4.2.RELEASE
Fixes gh-664
2016-11-10 15:38:38 -06:00
Rob Winch
e9924d27a1 Update to Spring 4.3.4.RELEASE
Fixes gh-663
2016-11-10 15:38:22 -06:00
John Blum
f0820c8038 Update to Spring Boot 1.4.1.RELEASE
* Upgrade to Spring Data GemFire 1.8.4.RELEASE
 * Upgrade to Spring Data MongoDB 1.9.4.RELEASE
 * Upgrade to Spring Framework 4.3.3.RELEASE

(Upgrade to Spring Data Redis 1.7.4.RELEASE failed)

Fixes gh-632
2016-11-10 15:37:25 -06:00
Rob Winch
7d680ff3ef Update to Spring Security 4.2.0
Fixes gh-662
2016-11-10 15:37:17 -06:00
Rob Winch
6d9885455b Explicit Spring IO Version 2016-10-13 08:12:24 -05:00
Rob Winch
06104c348d Revert "Add Apache Geode support (#366)"
This revert is done because Geode is not supported by Spring IO yet.

This reverts commit 1256a94d7e.

# Conflicts:
#	gradle.properties
#	settings.gradle
2016-10-13 08:12:24 -05:00
Vedran Pavic
090742350c Polish RedisOperationsSessionRepository 2016-09-30 14:17:52 -05:00
Rob Winch
a290b11019 Add OrientDB to What's New 2016-09-30 10:09:30 -05:00
Miron Aseev
eabad84ba8 Add a link to Spring Session OrientDB project 2016-09-30 10:07:44 -05:00
Rob Winch
4e33b7740c Update to Spring Security 4.2.0.M1 2016-09-23 15:56:06 -05:00
Spring Buildmaster
f8967c4c13 Next development version 2016-09-14 19:08:20 +00:00
Spring Buildmaster
245e634bea Release version 1.3.0.M2 2016-09-14 19:08:14 +00:00
Rob Winch
7a2914323f Update to Hazelcast 3.6.5
Issue gh-544
2016-09-14 11:32:03 -05:00
Vedran Pavic
6e04d903ae Add HazelcastSessionRepository
This commit improves existing Hazelcast support, which is based on
MapSessionRepository, with dedicated HazelcastSessionRepository
that implements the FindByIndexNameSessionRepository contract.

Also a new hazelcast-spring-session module was added to provide
dependency management for Hazelcast support.

Fixes gh-544
2016-09-14 11:22:56 -05:00
Spring Buildmaster
d8c3a4dd61 Next development version 2016-09-13 21:21:21 +00:00
Spring Buildmaster
997813088a Release version 1.3.0.M1 2016-09-13 21:21:14 +00:00
Rob Winch
5ecf390932 Add spring.session.cleanup.cron.expression
Fixes gh-616
2016-09-13 10:41:31 -05:00
Rob Winch
8167b43e63 Update to AssertJ 2.5.0 2016-09-13 10:29:14 -05:00
Rob Winch
1f7193f32d Polish
Issue gh-434
2016-09-12 16:02:45 -05:00
Rob Winch
0e1d81f509 Add Spring Security jackson2
Issue gh-434
2016-09-12 16:02:40 -05:00
Jitendra Singh Bisht
8b97a32db2 Mix-ins added for Jackson Serialization/deserialization
Fixes gh-434
2016-09-12 16:02:21 -05:00
Vedran Pavic
d3379029bb Fix custom expire for JdbcOperationsSessionRepository.cleanUpExpiredSessions
Currently, JdbcOperationsSessionRepository#cleanUpExpiredSessions only considers
the repository defined max inactive interval which causes incorrect cleanup of
sessions that define custom inactive interval. This commit fixes the problem by
delegating calculation of deletion interval to the underlying SQL DELETE statement.

Fixes gh-580
2016-09-12 13:09:36 -05:00
Vedran Pavic
26eca5b448 Add custom SQL JdbcOperationsSessionRepository
This commit allows custom SQL queries for
JdbcOperationsSessionRepository.

Fixes gh-609
2016-09-12 13:08:15 -05:00
Vedran Pavić
6335894e13 Restore Unix (LF) line endings in CookieHttpSessionStrategyTests (#625) 2016-09-12 12:54:12 -05:00
Vedran Pavic
34948d6451 Improve JDBC integration tests 2016-09-12 11:28:05 -05:00
Eric Deandrea
bff0f8f845 Fix CookieHttpSessionStrategy encodeUrl
Fix to https://support.pivotal.io/tickets/30469

I do notice 2 bugs in the spring-session code, both inside the
CookieHttpSessionStrategy class.

The first, using their own sample application, after getting a new
session alias from the HttpSessionManager, it calls
HttpSessionManager.encodeURL and then stores the output into a request
attribute which is later rendered. However, in a Spring MVC
application, a Controller may want to issue a redirect to a
newly-encoded URL, like this:

HttpSessionManager sessionManager = (HttpSessionManager)
request.getAttribute(HttpSessionManager.class.getName());
String newSessionAlias = sessionManager.getNewSessionAlias(request);
String currentSessionAlias =
sessionManager.getCurrentSessionAlias(request);

return String.format("redirect:%s",
sessionManager.encodeURL("/effectiveUser", newSessionAlias));

The problem here is that Spring MVC will funnel the redirect back
through
CookieHttpSessionStrategy.MultiSessionHttpServletResponse.encodeRedirect
URL, which then looks up the current alias & re-encodes with that
alias, effectively replacing the new alias which the controller added.
The fix would be for the
CookieHttpSessionStrategy.MultiSessionHttpServletResponse to examine
the inputted url for the appropriate parameter & don't re-encode if it
finds it there.

The second issue is that inside CookieHttpSessionStrategy line 346
(return path + "?" + query;) is that if there is no query string (on
the default session alias) then the resulting URL will end in a ? with
nothing after it. In that situation the ? should be removed as well
because the query string is empty.
2016-09-12 11:03:57 -05:00
Rob Winch
2052ec8d44 Polish JDBC Bean ClassLoader
Issue gh-610
2016-09-07 11:08:59 -05:00
Vedran Pavic
cbd96999e0 JDBC uses Bean ClassLoader
This commit addresses the issue with deserializing JDBC sessions in Spring Boot
applications that use DevTools. Previously, such configuration would cause
`ClassCastException` when deserializing JDBC sessions due to app class loader
being used instead of restart class loader.

Fixes gh-610
2016-09-07 11:08:58 -05:00
Rob Winch
61492c4ae1 Add ability to set delimiters for CookieHttpSessionStrategy
Fixes gh-615
2016-09-06 23:20:53 -05:00
Rob Winch
97fef0f9bd Polish Base64 DefaultCookieSerializer Support
Issue gh-611
2016-09-06 21:20:20 -05:00
Vedran Pavic
2792d2a0e9 Add DefaultCookieSerializer Base64 Support
Fixes gh-611
2016-09-06 21:19:52 -05:00
Joris Kuipers
2724b333b3 Spring security session registry (#473)
*  Spring Security Concurrent Session Integration #65

 add SpringSessionBackedSessionRegistry

*  Spring Security Concurrent Session Integration #65

 add documentation

*  Spring Security Concurrent Session Integration #65

support marking SessionInformations as expired before deleting the Session
2016-09-02 14:01:41 -05:00
Vedran Pavić
8db7d394ba Fix broken samples (#606) 2016-09-02 13:44:17 -05:00
Vedran Pavić
3e293e8b54 Polish JDBC configuration (#608) 2016-09-02 13:42:36 -05:00
Rob Winch
de7bb05fc1 Remove only master from .travis.yml 2016-08-30 09:16:34 -05:00
John Blum
3ee4c5b5d0 Register non-anonymous, named Instantiators for GemFireSession and GemFireSessionAttributes (#594)
Fixes gh-594 & gh-595
2016-08-17 17:35:00 -07:00
John Blum
acf37fc8f4 Fix deserialization issue caused by unregistered Instantiator (#595)
Fixes gh-594 & gh-595
2016-08-17 09:22:50 -07:00
John Blum
1256a94d7e Add Apache Geode support (#366)
Closes gh-365, gh-366
2016-08-15 14:53:12 -05:00
Vedran Pavić
bbb94361f8 Optimize save operation in JdbcOperationsSessionRepository (#582)
This commit improves saving of new sessions to only execute batch update operation if there really are any attributes to save.
2016-08-15 14:25:37 -05:00
John Blum
9af3f1fcec Enable named Pool configuration on cache client 'Session' Region (#487)
Fixes gh-484
2016-08-15 14:14:34 -05:00
Vedran Pavić
5b70d55a21 Improve result set extraction in JdbcOperationsSessionRepository (#577)
Previously, result set extraction was performed by a `RowMapper` implementation that required scrollable result sets support which caused compatibility issues with some database vendors.

This commit improves result set extraction by using a `ResultSetExtractor` which does not require scrollable result sets.
2016-08-15 13:34:12 -05:00
Vedran Pavić
9133e337e6 Use webjars-locator in samples (#587) 2016-08-15 13:25:53 -05:00
Rob Winch
9f36fd69ee Version is 1.3.0.BUILD-SNAPSHOT 2016-06-29 22:52:40 -05:00
Spring Buildmaster
e684f58403 Next development version 2016-06-30 03:05:34 +00:00
Spring Buildmaster
508e3e90a7 Release version 1.2.1.RELEASE 2016-06-30 03:05:19 +00:00
Vedran Pavic
0018a2f772 Add database schema drop scripts
Fixes gh-565
2016-06-29 21:21:53 -05:00
Rob Winch
e9f097db5c Gemfire Boot Sample uses Random Port
Previously, the Gemfire Boot Sample didn't select a random port for
Tomcat to run on.

Issue gh-496
2016-06-29 21:02:10 -05:00
Rob Winch
7e41e40762 Fix Gemfire ClientConfig BeanFactoryPostProcessor
@Bean method ClientConfig.propertySourcesPlaceholderConfigurer was
non-static and returns an object assignable to Spring's
BeanFactoryPostProcessor interface. This will result in a failure to
process annotations such as @Autowired, @Resource and @PostConstruct
within the method's declaring @Configuration class.

This commit makes the method static to avoid the problem.
2016-06-29 21:02:10 -05:00
Rob Winch
dc553104c6 Fix SessionRepositoryFilterTests doFilterLastAccessedTime assertion
Relax the assertion so that it is not exact to avoid timing issues.

Fixes gh-519
2016-06-29 16:24:52 -05:00
Vedran Pavic
2b4a349b1a Use webjars for static resources in sample apps 2016-06-29 12:59:54 -05:00
Rob Winch
4a1f132e07 Add JdbcOperationsSessionRepository overflow test
Add test for ensuring
JdbcOperationsSessionRepository.cleanupExpiredSessions does not have an
overflow when setting the max inactive interval to large values.

Issue gh-564
2016-06-29 11:58:18 -05:00
pasali
940555653c Fix overflow in JdbcOperationsSessionRepository
Changed maxInactiveIntervalSeconds to long in order to prevent integer
overflow in cleanUpExpiredSessions method.

Fixes gh-564
2016-06-29 11:56:03 -05:00
Rob Winch
82cb417910 Boot Gemfire Sample JDK7 compatible
Issue gh-496
2016-06-29 11:22:16 -05:00
Rob Winch
808a550343 Polish Boot Gemfire Sample
Issue gh-496
2016-06-29 11:08:05 -05:00
Rob Winch
cc08f83ce0 Fix Gemfire Eclipse & Checkstyle 2016-06-29 11:07:58 -05:00
John Blum
53c81de5e3 Add Spring Session Data GemFire with Spring Boot sample
Fixes gh-496
2016-06-29 10:34:58 -05:00
Vedran Pavić
dd268992a8 Update GitHub Pull Request template
Remove the CLA checkbox

Fixes gh-558
2016-06-21 14:04:39 -05:00
Eddú Meléndez
00004e2f50 Polish build.gradle samples
Disable publishing samples artifacts.

Fixes gh-518
2016-06-19 03:01:35 +10:00
Rob Winch
afb9a11abf Update to CLA tooling 2016-06-08 21:58:20 -05:00
Vedran Pavić
fb356f02ff SessionRepositoryFilterTests Sleeps for 50 ms 2016-06-06 08:45:19 -05:00
Vedran Pavić
509fb99a5d Improve JDBC transaction management doc 2016-05-20 11:56:58 -05:00
Vedran Pavić
2aff5b0377 Fix EnableRedisHttpSession.redisFlushMode javadoc
Fix EnableRedisHttpSession.redisFlushMode javadoc
2016-05-16 08:04:47 -05:00
Spring Buildmaster
016a007d12 Next development version 2016-05-12 01:28:55 +00:00
Spring Buildmaster
2a3ec2f4b9 Release version 1.2.0.RELEASE 2016-05-12 01:28:40 +00:00
Vedran Pavić
458ac6e700 Increase default size for attribute_name column (#515) 2016-05-06 17:03:40 -05:00
Vedran Pavić
33321d2e53 Polish MongoHttpSessionConfigurationTests
This improves test coverage and makes the MongoHttpSessionConfigurationTests
more consistent with analogous tests for other repositories.

Fixes gh-503
2016-05-02 15:11:54 -05:00
Spring Buildmaster
20130f8e8a Next development version 2016-04-27 01:19:07 +00:00
Spring Buildmaster
03f008f283 Release version 1.2.0.RC3 2016-04-27 01:18:46 +00:00
Vedran Pavic
00e7110594 Implement individual attribute persistence in JdbcOperationsSessionRepository 2016-04-26 14:29:23 -05:00
Eddú Meléndez Gonzales
f973e63fce Set collectionName attribute in MongoOperationsSessionRepository
Previous to this commit, collectionName could be set in
MongoHttpSessionConfiguration but it was never used. Now, attribute
can be set into MongoOperationsSessionRepository to take effect.

See gh-489
2016-04-26 14:27:02 -05:00
Eddú Meléndez
edbf8bf587 Expose attributes in MongoHttpSessionConfiguration
Attributes collectionName and maxInactiveIntervalInSeconds can be set
now.

Fixes gh-489
2016-04-20 13:49:18 -05:00
Johnny Lim
234f6c954a Remove duplicate words
Remove duplicate words

Fixes gh-494
2016-04-18 23:27:48 -05:00
Rob Winch
e0417523f6 Add tableName setter
Add tableName setter for JdbcHttpSessionConfiguration
2016-04-18 23:18:58 -05:00
Eddú Meléndez
5865b0a715 Add tableName setter for JdbcHttpSessionConfiguration
See gh-488
2016-04-19 01:00:40 +10:00
Vedran Pavić
67201417df Add compile dependency to commons-logging (#476) 2016-04-11 10:55:10 -05:00
Vedran Pavić
01a149737e Add GitHub issue/PR tempaltes
Add
2016-04-11 10:54:13 -05:00
John Blum
2d6f505a30 Update to Spring Data Hopper
Fixes gh-470
2016-04-11 09:31:05 -05:00
Rob Winch
7c616a1adf Redis save does nothing if nothing has changed
Fixes gh-467
2016-04-06 15:38:34 -05:00
Spring Buildmaster
61b01d9ecd Next development version 2016-04-06 14:26:42 +00:00
Spring Buildmaster
f9163d94fd Release version 1.2.0.RC2 2016-04-06 14:26:36 +00:00
Vedran Pavić
3f819a94b1 Enable transaction management for JdbcOperationsSessionRepository operations 2016-04-05 23:39:36 -05:00
Rob Winch
1e1d24895c Merge pull request #454 from vpavic/jdbc-boot-sample
Add JDBC Spring Boot sample
2016-04-04 08:36:27 -05:00
Rob Winch
e134a4cdb8 Merge pull request #463 from vpavic/fix-h2-console-urls
Make H2 console URL consistent across sample projects
2016-04-04 07:35:45 -05:00
Rob Winch
75006cd7dd Merge pull request #462 from vpavic/gh-457
Update WebSocket sample to use H2 console auto-configuration
2016-04-04 07:34:09 -05:00
Vedran Pavic
9c8f8894e1 Add JDBC Spring Boot sample 2016-04-03 20:57:16 +02:00
Vedran Pavic
99db45ea72 Make H2 console URL consistent across sample projects 2016-04-01 22:36:24 +02:00
Vedran Pavic
c07583bd47 Update WebSocket sample to use H2 console auto-configuration
Fixes gh-457.
2016-04-01 22:26:13 +02:00
Rob Winch
cce8dac4b7 Fix WebSocket AbstractMethodError
Fixes gh-460
2016-04-01 14:27:08 -05:00
Rob Winch
5bde226ecc Fix Eclipse compile errors
* Most web.xml servlet API versions updated to 3.0 for ASYNC support
* httpsession-xml is left at 2.5 to ensure compatability & remove ASYNC
* Remove @Override on interface override
2016-04-01 11:56:27 -05:00
Rob Winch
f8f6ee20c0 Externalize sample.gradle 2016-03-30 10:23:11 -05:00
Rob Winch
3bb96e8e82 Remove Wrapper from Gradle Sample
Issue gh-246
2016-03-30 10:22:59 -05:00
Rob Winch
b7367680cb Fix Package Names in Grails Sample
Issue gh-246
2016-03-30 10:22:59 -05:00
Rob Winch
a26a21b663 Add Grails 3 Sample to What's New
Issue gh-246
2016-03-30 09:28:03 -05:00
Rob Winch
3825a46418 Polish Grails 3 Sample
Issue gh-246
2016-03-29 20:58:26 -05:00
Eric Helgeson
779277b16d Add Grails Sample
Fixes gh-246
2016-03-29 20:58:26 -05:00
Rob Winch
b97306a83d Merge pull request #450 from vpavic/gh-445
Fix loading of JdbcSession's lastAccessedTime attribute
2016-03-28 15:06:49 -05:00
Rob Winch
6b13111079 Merge pull request #451 from vpavic/polish-tests
Polish JdbcHttpSessionConfigurationTests
2016-03-28 11:15:15 -05:00
Vedran Pavic
63006db45d Fix loading of JdbcSession's lastAccessedTime attribute
Fixes gh-445.
2016-03-28 18:13:16 +02:00
Vedran Pavic
0a99e065ff Polish JdbcHttpSessionConfigurationTests 2016-03-28 17:49:02 +02:00
Scott Carlson
bd2d846917 Add Dispatcher types to web.xml
Fixes gh-443
2016-03-28 09:16:13 -05:00
Rob Winch
79928bd7fe Merge pull request #436 from vpavic/improve-mongo-it
Use Flapdoodle Embedded MongoDB for integration tests
2016-03-25 09:30:40 -05:00
Vedran Pavic
903cac492e Use Flapdoodle Embedded MongoDB for integration tests and samples 2016-03-25 07:27:33 +01:00
Rob Winch
3980be349b Merge pull request #440 from lowzj/master_fix-typo
Fix SessionRepositoryFilter comment typo
2016-03-23 08:27:23 -05:00
lowzj
128e0c4d47 Fix SessionRepositoryFilter comment typo 2016-03-23 19:32:05 +08:00
Rob Winch
37bc6a352f Merge pull request #437 from vpavic/improve-build
Externalize H2 database dependency version
2016-03-20 11:10:24 -05:00
Vedran Pavic
b88f48f01d Externalize H2 database dependency version 2016-03-20 01:56:35 +01:00
Spring Buildmaster
028e277fc9 Next development version 2016-03-16 20:32:18 -07:00
Spring Buildmaster
904cdf4e65 Release version 1.2.0.RC1 2016-03-16 20:32:03 -07:00
Rob Winch
b58ea03a3b Polish JdkMongoSessionConverter
* Serializer/Deserializer -> Converter API
* Verify/Test null constructor arguments

Issue gh-431
2016-03-16 21:06:57 -05:00
Jakub Kubrynski
9014ac9060 JdkMongoSessionConverter supports custom ClassLoader
Fixes gh-431
2016-03-16 20:35:39 -05:00
Rob Winch
7a82915a98 Polish Mongo Documentation
* Externalize documentation code for testing
* Polish wording
* Start each new sentence with new line

Issue gh-430
2016-03-16 09:12:35 -05:00
Rob Winch
e16224583c Fix Eclipse copyright start date 2016-03-16 09:12:35 -05:00
Jakub Kubrynski
96861ea535 Extend Mongo documentation 2016-03-16 09:12:35 -05:00
Rob Winch
de3d04fb29 Merge pull request #429 from eddumelendez/gh-427
Update to Sonarqube Plugin
2016-03-15 08:41:02 -05:00
Eddú Meléndez
68823eec29 Update to Sonarqube Plugin
See gh-427
2016-03-15 07:59:18 +10:00
Rob Winch
a58d4f64fb Merge pull request #428 from vpavic/update-docs
Fix incorrect link in What's new section
2016-03-14 16:22:40 -05:00
Vedran Pavic
40ad82806c Fix incorrect link in What's new section 2016-03-14 22:08:30 +01:00
Rob Winch
6db7d45f65 Add gh-423 to What's New in 1.2
Issue gh-420
2016-03-14 13:34:38 -05:00
Rob Winch
bbbdf939e8 Polish JacksonMongoSessionConverter
Issue gh-416
2016-03-14 13:29:17 -05:00
Jakub Kubrynski
e23398b890 Add JacksonMongoSessionConverter
Fixes gh-416
2016-03-14 13:29:17 -05:00
Rob Winch
5cfbeae161 Add Travis Badge
Issue gh-394
2016-03-14 12:42:03 -05:00
Rob Winch
c6e71a90bb Add Travis
Fixes gh-394
2016-03-14 12:30:02 -05:00
Rob Winch
195228ff5a Fix checkstyle
Fixes gh-423
2016-03-14 10:41:40 -05:00
Øyvind Horneland
831d2f4152 SessionRepositoryFilter caches null session lookup
If a session cannot be found by id, we will cache that result for any
subsequent calls to getSession(false) for the duration of this request.

Fixes gh-423
2016-03-14 10:40:25 -05:00
Rob Winch
4f7728f5b5 Fix Checkstyle Eclipse Settings
Issue gh-417
2016-03-14 00:13:46 -05:00
Rob Winch
11e418526e Add What's new in 1.2
Fixes gh-420
2016-03-11 10:21:23 -06:00
Erin Drummond
49f24e493d Fix JdbcOperationsSessionRepository session cleanup
Make JDBC session cleanup logic actually cleanup expired sessions
as opposed to all sessions

Fixes gh-421
2016-03-11 09:28:18 -06:00
Vedran Pavic
bd6d5bf419 Add JDBC documentation
Fixes gh-415
2016-03-10 09:24:57 -06:00
Rob Winch
9b989c4a3e Merge pull request #414 from vpavic/fix-docs-test
Fix bad reference in IndexDocTests
2016-03-07 16:19:28 -06:00
Rob Winch
68ddcef83e Fix Javadoc 2016-03-07 16:08:54 -06:00
Rob Winch
2592905b41 Add Eclipse settings
Fixes gh-417
2016-03-07 15:37:22 -06:00
Rob Winch
4be47f6b40 Add Agregate checkstyle task
Issue gh-393
2016-03-07 15:37:12 -06:00
Rob Winch
35b5fcdc75 Refine Checkstyle
Refine the Checkstyle rules slightly

Fixes gh-393
2016-03-07 15:36:56 -06:00
Vedran Pavic
4d010b23b8 Add Checkstyle to build
Fixes gh-393
2016-03-07 15:36:44 -06:00
Rob Winch
f0200696ef Additional Checkstyle Fixes
Issue gh-393
2016-03-07 15:36:17 -06:00
Vedran Pavic
7f3302253b Prepare codebase to adhere to Checkstyle rules
Issue gh-393
2016-03-07 15:33:42 -06:00
Rob Winch
9e3bcafa75 Use @SpringBootApplication 2016-03-07 12:45:06 -06:00
Rob Winch
216bfd7355 MongoSessionConverter -> AbstractMongoSessionConverter
Using Abstract prefix follows conventions for abstract classes
beginning with Abstract. This also opens up the door to introduce
a MongoSessionConverter interface in the future.

Issue gh-17
2016-03-07 12:45:05 -06:00
Jakub Kubrynski
34cebc3df6 Add MongoOperationsSessionRepository
Fixes gh-17
2016-03-07 12:45:05 -06:00
Rob Winch
7b28b214ff Polish JdbcOperationsSessionRepository
* Fix whitepspaces
* Remove Override from interfaces

Issue gh-364
2016-03-07 09:25:30 -06:00
Rob Winch
8f8cfe5d79 JdbcOperationsSessionRepository uses ConversionService
Issue gh-364
2016-03-07 09:18:21 -06:00
Vedran Pavic
e0e29eab35 Fix bad reference in IndexDocTests 2016-03-06 23:18:51 +01:00
Vedran Pavic
cd38e307e0 Add JdbcOperationsSessionRepository
This commit provides implementation of SessionRepository based
on Spring's JdbcOperations interface.

@EnableJdbcHttpSession annotation is provided to ease the
configuration, together with spring-session-jdbc BOM and schema
creation scripts for all major databases.

Fixes gh-364
2016-03-04 11:24:06 -06:00
Rob Winch
091d0d8d9f DefaultCookieSerializer ignores null Cookie value
Previously a null Cookie value was returned by DefaultCookieSerializer
readCookieValues(). This could cause NullPointerExeptions later on.

This commit ignores cookies with a null value.

Fixes gh-392
2016-03-04 10:22:33 -06:00
John Blum
3300b58afe Fix GemFire TODOs
Upgrades Spring Session to use Spring Data GemFire 1.7.4.RELEASE
and addresses all TODOs.

Fixes gh-403
2016-02-29 21:07:00 -06:00
John Blum
00af8456d4 @DirtiesContext for GemFire integration tests
Applies @DirtiesContext to all Spring Session Data GemFire integration
tests to ensure that no lingering GemFire (peer or client) cache
instances cause conflicts on downstream tests.

Fixes gh-396
2016-02-29 20:46:59 -06:00
Rob Winch
aeb0add50e Fix GemFire compile in Eclipse
Fixes gh-399
2016-02-29 15:47:01 -06:00
Rob Winch
671bfa0444 Revert "Fix GemFire compile in Eclipse"
This reverts commit 8bf3a251bb.
since the fixes to the tests are incorrect.
2016-02-29 15:46:44 -06:00
Rob Winch
8bf3a251bb Fix GemFire compile in Eclipse
Fixes gh-399
2016-02-29 14:38:18 -06:00
Rob Winch
5d1c60711f Merge pull request #397 from vpavic/update-docs
Fix minor documentation errors
2016-02-29 12:59:41 -06:00
Vedran Pavic
32cf8ad7ad Fix minor documentation errors 2016-02-28 22:25:42 +01:00
Rob Winch
d3139b04e1 Update to 1.2.0.BUILD-SNAPSHOT 2016-02-25 14:40:17 -06:00
Spring Buildmaster
cc0bfc2299 Next development version 2016-02-25 11:15:24 -08:00
Spring Buildmaster
c0a5916413 Release version 1.1.0.RELEASE 2016-02-25 11:15:17 -08:00
Rob Winch
11ca552625 Update Dependency Versions
Fixes gh-386
2016-02-25 13:08:44 -06:00
Rob Winch
8e4ed22974 Add gradle-versions-plugin
This will make finding updates easier.

Fixes gh-385
2016-02-24 17:05:52 -06:00
John Blum
53d7c84f73 Fix ClassCastException Token$Tombstone
Fixes gh-373
2016-02-24 17:01:26 -06:00
Rob Winch
1871915c62 Update SessionEventRegistry to store all events
This ensures that if multiple events are published, we wait until the event
with the expected session id is fired.

Fixes gh-382
2016-02-23 15:41:57 -06:00
Rob Winch
d5259bf0e9 Fix RedisSessionExpirationPolicy logger name
Fixes gh-329
2016-02-23 14:31:00 -06:00
Rob Winch
12b1dda24c Remove EnableScheduling from SpirngHttpsessionConfiguration
Fixes gh-377
2016-02-22 22:43:15 -06:00
Rob Winch
fad66c5cd2 Spaces to Tabs 2016-02-22 22:12:47 -06:00
Rob Winch
e338e70972 Remove unnecessary JS from boot sample
Fixes gh-378
2016-02-22 22:03:43 -06:00
Rob Winch
4c01782d6c Polish gh-314
* Formatting
* Place lock inside test object
2016-02-22 13:23:42 -06:00
Vladimir Tsanev
5bf12fdd93 Custom task executors for RedisMessageListenerContainer
Tasks executors used by redisMessageListenerContainer can now optionally
be overriden with beans named springSessionRedisTaskExecutor and/or
springSessionRedisSubscriptionExecutor.

Fixes gh-314
2016-02-22 13:22:53 -06:00
Rob Winch
d209a10514 Clean up principal index immediately on delete
Previously index was cleaned up only in the Redis Keyspace Notification.
This meant there was a delay in removing the index. This does not cause
a bug since we verify sessions exist and are not expired when we look up
sessions by index. However, it could be improved.

This commit ensures that the index is cleaned up immediately on session
deletion.

Fixes gh-367
2016-02-15 08:32:59 -06:00
Spring Buildmaster
1d0eb68f70 Next development version 2016-02-10 21:17:19 -08:00
Spring Buildmaster
b64d0c0d3c Release version 1.1.0.RC1 2016-02-10 21:17:11 -08:00
Rob Winch
305ce1ae65 Update What's New Section
Fixes gh-341
2016-02-10 22:29:03 -06:00
Rob Winch
aed79ddbf7 Fix wait time in expireFiresSessionExpiredEvent 2016-02-10 10:23:05 -06:00
Rob Winch
3b5fc5e9cc Fix Hazelcast saveUpdatesTimeToLiveTest 2016-02-10 10:20:54 -06:00
Rob Winch
e0bbbbcd45 Add RedisFlushMode
This commit allows Redis support to flush lazily (already
supported) or immediately (new).

Fixes gh-273
2016-02-10 08:49:01 -06:00
Rob Winch
5f23b3c272 Null check for hazelcastInstance
Previously if the hazelcastInstance failed to start we would get a
NPE when the tests ended. This would mask the original issue of starting
the hazelcastInstance.

This commit ensures that hazelcastInstance is non-null before trying to
shut it down.

Polish gh-360
Relates to gh-360
2016-02-09 09:08:24 -06:00
Artem Bilan
bf7729b936 Close only used HazelcastInstance
* It might be dangerous to shutdownAll() HazelcastInstances from the
test-case, especially when we are ran in with the integration test and on
the CI environment.
Therefore extract the instance variable in the test and close only it from
the @AfterClass
* Fix typo in the AbstractHazelcastRepositoryITests test method name
* Increase lock wait timeout to the 10 seconds in the SessionEventRegistry.
Looks like 3 seconds isn't enough for my Windows machine.

Fixes gh-358 gh-360
2016-02-09 09:08:09 -06:00
Vedran Pavic
7c53558454 Add integration tests for Hazelcast client use case
Fixes gh-356
2016-02-08 16:16:22 -06:00
Mark Anderson
f2443f5e21 Support External Hazelcast instance
Previously the Hazelcast support only worked with
embedded Hazelcast instances.

This commit ensures that Hazelcast support works
with external Hazelcast instances.

Fixes gh-339
2016-02-08 16:00:37 -06:00
Rob Winch
2b5386ad98 Polish GemFire findByIndexNameAndValue
Issue gh-353
2016-02-08 15:36:42 -06:00
John Blum
c2b407189e Add generic GemFire findByIndexNameAndIndexValue support
Fixes gh-353
2016-02-08 15:36:08 -06:00
Rob Winch
7de11753a9 Add Debug Logging for Session Creation
Fixes gh-323
2016-02-04 15:40:50 -06:00
Rob Winch
2d6a474c2c Change RedisSerializer qualifier to springSessionDefaultRedisSerializer
Fixes gh-350
2016-02-04 15:23:05 -06:00
Rob Winch
cc1d3a7e9e Add test for Redis onMessage w/ custom serializer
Issue gh-309
2016-02-04 14:12:24 -06:00
Janne Valkealahti
fe17e3fcc0 Use correct RedisSerializer
- Remove hardcoded JdkSerializationRedisSerializer from onMessage
  in favour of same defaultRedisSerializer user configured as this
  is most likely the one which works.
- Fixes #309
2016-02-04 14:01:26 -06:00
Rob Winch
ef904a4544 Add Code of Conduct
Fixes gh-351
2016-02-01 14:27:19 -06:00
Rob Winch
207e4e27b3 Add Gitter
Fixes gh-349
2016-01-31 23:00:11 -06:00
Rob Winch
bda5c85acf Cookie Value now sessionId + "." jvmRoute
If jvmRoute is set on DefaultCookieSerializer, then it sets the cookie
value to be:

sessionId + "." jvmRoute

Fixes gh-326
2016-01-29 16:50:22 -06:00
Rob Winch
c9acd3c812 Remove Compiler Warnings 2016-01-29 16:27:40 -06:00
Rob Winch
f20acbf9b9 Automatically Index SecurityContext
Fixes gh-266
2016-01-29 16:27:19 -06:00
Rob Winch
ad09b498a3 FindByPrincipalNameSessionRepository -> FindByIndexNameSessionRepository
Fixes gh-342
2016-01-29 13:54:11 -06:00
Rob Winch
7618aafb90 Fix Javadoc warnings
Fixes gh-347
2016-01-29 13:23:35 -06:00
Rob Winch
7a3bf533b3 Remove Compiler Warnings
Fixes gh-346
2016-01-29 12:13:03 -06:00
Rob Winch
723ea5e2e0 Remove GemFire ClientConfig compile time warning
Issue gh-346
2016-01-29 07:50:09 -06:00
Rob Winch
7463592988 Fix RedisOperationsSessionRepository.findByPrincipalName index
Previously, the index was not properly cleaned up when it was changed. This
commit ensures that the index is removed when the index name changes.

Fixes gh-343
2016-01-28 21:42:29 -06:00
Rob Winch
198acc0648 Remove compile time warning
Remove the MapSession.getAttribute compile time warning.

Issue gh-346
2016-01-28 21:42:00 -06:00
Rob Winch
f53e991d02 Remove spring-shell from spring-data-gemfire
Fixes gh-344
2016-01-28 20:26:49 -06:00
Rob Winch
22f6f9dd72 Polish GemFire
* Remove Compile Warnings
* Correct @since
* Add tests
* Add to What's New in documentation
2016-01-28 16:34:40 -06:00
John Blum
019e0083b0 Add GemFire Support
Fixes GH PR #148 and PR #308 implementing a GemFire Adapter to support
clustered HttpSessions in Spring Session.

* Resolve SGF-373 - Implement a Spring Session Adapter for GemFire backing
a HttpSession similar to the Redis support.
* Add Spring Session annotation to enable GemFire support with
@EnableGemFireHttpSession.
* Add extesion of SpringHttpSessionConfiguration to configure GemFire using
GemFireHttpSessionConfiguration.
* Add implementation of SessionRepository to access clustered, replicated
HttpSession state in GemFire with GemFireOperationsSessionRepository.
* Utilize GemFire Data Serialization framework to both replicate
HttpSession state information as well as handle deltas.
* Utilize GemFire OQL query to lookup arbitrary Session attributes by name,
and in particular the user authenticated principal name.
* Implment unit and integration tests, and in particular, tests for both
peer-to-peer (p2p) and client/server topologies.
* Set initial Spring Data GemFire version to 1.7.2.RELEASE, which depends
on Pivotal GemFire 8.1.0.
* Add documentation, Javadoc and samples along with additional Integration
Tests.

Fixes gh-148
2016-01-28 16:17:54 -06:00
Rob Winch
1931da83a5 Fix spring io tests
Issue gh-338
2016-01-25 20:42:07 -06:00
Rob Winch
5dff0ff37b Migrate from fest-assert to assertj
Fixes gh-338
2016-01-25 19:41:54 -06:00
Rob Winch
1003821dad Merge pull request #337 from anujbahuguna/master
Upgrade to Gradle Wrapper 2.10
2016-01-24 22:19:03 -06:00
Anuj Bahuguna
5399b7c299 Upgrade to Gradle Wrapper 2.10 2016-01-23 12:20:05 +00:00
Rob Winch
969053fa87 SessionRepositoryMessageInterceptor updates lastAccessedTime
Fixes gh-325
2015-12-08 08:37:50 -06:00
Rob Winch
eb646ef5d8 SessionRepositoryFilter sets maxInactiveIntervalInSeconds on create
Fixes gh-324
2015-12-04 15:44:52 -06:00
Rob Winch
f14942edb0 Add Tests for Lazy Updates of Spring Session
Ensure that the session is only modified if it is accessed. This allows for
optimizations to ensure that for things like static resources there is
no need for hitting a data store.
2015-12-04 15:44:52 -06:00
Rob Winch
18a07abeb4 Merge pull request #316 from tsachev/gh-315
Don't send expirations del events when expiration time does not change.
2015-12-03 16:57:51 -06:00
Rob Winch
e766ad774d Make ServletContext Optional
Fixes gh-318
2015-12-03 16:46:41 -06:00
Rob Winch
b38cc74764 Include alias unless single id and default alias
Fixes gh-321
2015-12-03 14:38:25 -06:00
Rob Winch
3df40dd8a9 Merge pull request #319 from izeye/patch-2
Upgrade to Gradle Wrapper 2.9
2015-11-28 14:18:56 -06:00
Johnny Lim
9d95638cf8 Upgrade to Gradle Wrapper 2.9 2015-11-28 11:37:02 +09:00
Vladimir Tsanev
d85624c58e Don't send expirations del events when expiration time does not change.
Expiration time is rounded up, so in case of many request per second from a
single session we can skip sending of delete event to reduce the traffic.
This is good because actually at the moment the redis repository is subscribed
for this events, even though it ignores them.

Fix gh-315
2015-11-26 14:43:31 +02:00
Rob Winch
5ca6377015 Next development version 2015-11-17 21:13:02 -06:00
Spring Buildmaster
33834fdf19 Release version 1.1.0.M1 2015-11-17 12:00:57 -08:00
Rob Winch
8e60c968a1 Add What's New in 1.1 Documentation
Fixes gh-304
2015-11-17 13:32:00 -06:00
Rob Winch
41298c490e Remove warnings 2015-11-17 11:03:36 -06:00
Rob Winch
ee09a9e863 Add ExpiringSession setLastAccessedTime
Previously SessionRepository had to update the lastAccessTime when it was
loaded. This prevented inspecting the last access time. For example,
listing all the sessions for a specific user. Furthermore, it is
unintuitive that a read operation would update attributes on the domain
model.

This change introduces ExpiringSession setLastAccessedTime to allow setting
the expiration on the interface. This means that the SessionRepositoryFilter
can update the last accessed time.

Fixes gh-272
2015-11-17 10:47:42 -06:00
Rob Winch
8bd29c1ab2 HttpSessionListener sessionCreated id contains invalid ':'
Fixes gh-305
2015-11-16 16:12:35 -06:00
Tommy Ludwig
f1d0350356 Remove unnecessary code
Code for a workaround that is no longer necessary was copied from
existing code. This removes that unnecessary code.
2015-11-16 11:21:31 -06:00
Rob Winch
2fa7ecdee5 Remove unnecessary RedisHttpSessionConfiguration code
Fixes gh-306
2015-11-16 11:13:31 -06:00
Rob Winch
8a5cba914b RedisOperationsSessionRepsitory only deletes once
Previously RedisOperationsSessionRepository incorrectly:

* Deleted the session
* Added the session
* Set the expiration to be 0

This commit ensures that if the expiration is 0 that the sesson is only
deleted.

Fixes gh-292
2015-11-16 10:52:39 -06:00
Rob Winch
5fbf333c61 Fix RedisOperationsSessionRepository Logger name 2015-11-16 10:26:44 -06:00
Rob Winch
33c9485031 Deprecate SessionMessageListener
Deprecate SessionMessageListener in favor of
RedisOperationsSessionRepository

Fixes gh-289
2015-11-16 10:25:55 -06:00
Rob Winch
2e82b311ee Fix custom MultiHttpSessionStrategy configuration
Fixes gh-303
2015-11-11 15:27:01 -06:00
Rob Winch
8c07537bec Add CookieSerializer Strategy
This allows for custom seralization of the Cookie.

Fixes gh-299
2015-11-11 15:25:27 -06:00
Tommy Ludwig
c701e1877e Fix NPE with EnableHazelcastHttpSession and add tests
This fixes the NPE caused by setting maxInactiveIntervalInSeconds = ""
on the EnableHazelcastHttpSession annotation. This is a basic scenario
that was not covered by integration tests. Tests configuring Hazelcast
with an XML file (probably the most common method in the wild) covering
the failing scenario and more have been added.
2015-11-09 10:10:39 -06:00
Rob Winch
7186878f84 Update to Gradle 2.9 rc1
Fixes gh-298
2015-11-05 09:30:12 -06:00
Rob Winch
aeef1417e1 Display Javadoc Errors 2015-11-05 09:30:12 -06:00
Rob Winch
76d341d6ca Polish Hazelcast
* This commit moves the hazelcast support into a parent package
so that it no longer impies a Spring Data dependency.
* Add guards on SessionEntryListener logger
* Remove getSessionMapName on HazelcastHttpSessionConfiguration
* Use setSessionMapName on HazelcastHttpSessionConfiguration
rather than field access
* Formatting polish
* Fix Javadoc

Issue gh-276
2015-11-05 09:30:03 -06:00
Tommy Ludwig
d1c00c6080 Add Hazelcast
Fixes gh-276
2015-11-04 16:13:20 -06:00
Rob Winch
a48864bf20 Fix EnableSpringHttpSession @since
Issue gh-231
2015-10-01 15:42:09 -05:00
Rob Winch
bebb596846 Merge pull request #284 from manderson23/master
Minor documentation fix
2015-08-31 15:42:19 -05:00
Mark Anderson
af5899fb1a Fix inconsistency in docs when describing delete/expired events for Redis. 2015-08-31 21:17:30 +01:00
Rob Winch
489a26c14c Allow default RedisSerializer to be specified
Fixes gh-283
2015-08-31 10:26:48 -05:00
Rob Winch
134d23bb07 Add jsp-api to security sample
Fixes gh-279
2015-08-25 15:11:28 -05:00
Rob Winch
8b5469ef5c Polish hazelcast-spring sample
* Add author for Mark Anderson created classes
* Add Apache License
* Add jsp-api dependency

Issue gh-274
2015-08-25 15:10:38 -05:00
Mark Anderson
e3414ef11e Add hazelcast-spring Sample
Fixes gh-274
2015-08-25 15:10:10 -05:00
Rob Winch
68ad464b67 Add minimal Contributor guidelines 2015-08-25 09:14:38 -05:00
Rob Winch
db2759c497 Add Eclipse Code Formatter 2015-08-19 20:45:00 -05:00
Rob Winch
8760d7037a Polish RedisOperationsSessionRepositoryITests 2015-08-19 11:52:44 -05:00
Rob Winch
d2f7ac4898 Polish RedisOperationsSessionRepositoryITests
* Add namespace so we don't receive events for other tests
* Delete Sessions so don't receive events later
* Clear Registry before every test
2015-08-19 11:43:25 -05:00
Rob Winch
5fd2c71559 Minimize MapSession's use of Secure Random
Previously when creating a MapSession from an existing session required
that UUID.randomUUID() be invoked. This could slow down the system
since it requires entropy.

MapSession now has a constructor that accepts the id which prevents
Secure random from being used when the session is already known.

Fixes gh-271
2015-08-19 11:38:17 -05:00
Rob Winch
6414d0ddb8 Update to 1.1 2015-08-18 15:18:53 -05:00
Rob Winch
ba97bdf2fa EnableSpringHttpSession updated to since 1.2 2015-08-18 15:10:58 -05:00
Rob Winch
6c82628402 Fix FindByUsernameSessionRepository indent
Issue gh-7
2015-08-18 12:42:12 -05:00
Rob Winch
16b65973b7 Add EnableSpringHttpSession
Fixes gh-231
2015-08-18 12:41:50 -05:00
Rob Winch
db45698e25 Support application specific prefix
Fixes gh-166
2015-08-17 14:44:22 -05:00
Rob Winch
6234dd5681 Refactor tests
Prepare the tests for supporting an application prefix to ensure
there are no regressions.

Issue gh-166
2015-08-17 14:44:22 -05:00
Rob Winch
00d50593c1 Add livereload to docs/build.gradle
Fixes gh-269
2015-08-17 14:44:14 -05:00
Rob Winch
881ca7c2d4 Support querying for sessions by user identifier
Fixes gh-7
2015-08-17 14:41:55 -05:00
Rob Winch
77eb6cfd71 Remove commons-httpclient:commons-httpclient
Fixes gh-268
2015-08-17 14:41:35 -05:00
Rob Winch
21065b23c0 Add HttpSessionListener Support
Fixes gh-4
2015-08-12 16:55:01 -05:00
Rob Winch
7693d0e624 Extract out ExpiringSessionHttpSession
This allows the HttpSession Spring Session adapter to be reused
for HttpSessionEvent.

Issue gh-4
2015-08-12 16:55:01 -05:00
Rob Winch
2503537e36 Fix reference indentation for REST subsections
Fixes gh-262
2015-08-12 16:55:01 -05:00
Rob Winch
d27d9a22bf Add SessionCreatedEvent
Fixes gh-261
2015-08-12 16:55:01 -05:00
Rob Winch
6e1ecadcc4 Add Sesison to SessionExpiredEvent
Fixes gh-260
2015-08-12 16:49:10 -05:00
Rob Winch
690fbc8d3f RedisOperations<String,ExpiringSession> -> RedisOperations<Object,Object>
The RedisOperations manages more than just ExpiringSessions, so we need
to make this more generice.

Fixes gh-259
2015-08-12 16:37:56 -05:00
Rob Winch
3ba07ec0a8 Polish add SessionDeletedEvent & SessionExpiredEvent
* Add @author Mark Anderson for proper credit
* SessionDestroyedEvent no longer abstract to preserve passivity

Issue gh-258
2015-08-12 16:34:35 -05:00
Mark Anderson
7518306975 Add SessionExpiredEvent and SessionDeletedEvent
Fixes gh-258
2015-08-12 16:24:18 -05:00
Spring Buildmaster
249361bb7f Next development version 2015-08-03 14:07:47 -07:00
Rob Winch
f633037d6b Document SessionRepsitoryFilter ordering
Fixes gh-172
2015-07-29 11:22:57 -05:00
Rob Winch
5dd36c95d6 Document Minimum Requirements
Fixes gh-115 gh-118 gh-119
2015-07-29 11:21:07 -05:00
Rob Winch
6b3f814cd6 Remove embeddedRedisVersion
Issue gh-248
2015-07-29 11:19:40 -05:00
Rob Winch
3a17a84434 Re-run Tests against Spring 3
Fixes gh-120
2015-07-29 11:18:51 -05:00
Rob Winch
916a5cb7dd Run integration tests in check task
Fixes gh-254
2015-07-29 11:17:18 -05:00
Rob Winch
d5484e15ca CookieHttpSessionStrategy only writes same Session once
If SessionRepositoryRequestWrapper.commitSession() is invoked twice
when a new session is created, then CookieHttpSessionStrategy will
add the same cookie twice. A couple examples of how this could happen:

* The response is committed and
  SessionRepositoryResponseWrapper.onResponseCommitted() invokes
  SessionRepositoryRequestWrapper.commitSession(). Then the finally
  block in SessionRepositoryFilter invokes
  SessionRepositoryRequestWrapper.commitSession() again.
* The new session is initialized and an Exception is thrown (i.e.
  gh-229). The SessionRepositoryFilter invokes
  SessionRepositoryRequestWrapper.commitSession() in the REQUEST
  dispatch. Then in the ERROR dispatch SessionRepositoryFilter invokes
  SessionRepositoryRequestWrapper.commitSession() invokes it again.

This commit ensures if the same Session is passed into
CookieHttpSessionStrategy multiple times within the same HttpServletRequest
it is only written once by keeping track of the sessions on a request
attribute.

Fixes gh-251
2015-07-28 15:01:51 -05:00
Rob Winch
3d93f2cf56 currentSession saved on HttpServletRequest attribute
Previously, if the following happened:

* New Session Created
* Exception thrown
* Exception processed by error handler within Servlet
* Error Handler used a session

The result would be two sessions were created. This means the
data from the first session was also lost. This happend
because ERROR dispatch is a separate Filter invocation where
the request is no longer wrapped.

This commit ensures that currentSession is saved on a
HttpServletRequest attribute so that the ERROR dispatch sees
that a session was already created.

Fixes: gh-229
2015-07-27 17:23:34 -05:00
Rob Winch
8929e85fb1 Fix Tests
These tests passed, but were technically incorrect. The
invalid tests were noticed when fixing gh-229.

Issue: gh-229
2015-07-27 17:11:04 -05:00
Rob Winch
f6c1407259 Ignore .rdb files 2015-07-27 15:26:18 -05:00
Rob Winch
b79913240d HttpServletRequest.changeSessionId() impacts previous references
Previously, if a user had a reference to an existing HttpSession and
changed the session id, it would not work. For example:

HttpSession s = request.getSession();
request.changeSessionId();
s.setAttribute(...);

This commit fixes holding on to a reference of an HttpSession when
the session id is changed.

Fixes gh-227
2015-07-27 15:25:58 -05:00
Rob Winch
4c24384243 Remove generic type from SpringSessionRepositoryFilterTests
If there is a generic type in tests, Eclipse cannot figure out that
it is a test. This makes it difficult to run in the IDE.
2015-07-27 15:13:49 -05:00
Rob Winch
1635ea90ca enableRedisKeyspaceNotificationsInitializer return type InitializingBean
Previously RedisHttpSessionConfiguration
enableRedisKeyspaceNotificationsInitializer return type was a package
protected class. This meant if someone extended
RedisHttpSessionConfiguration they got IllegalAccessErrors.

This changes the return type to InitializingBean.

Fixes gh-109
2015-07-25 08:09:51 -05:00
Rob Winch
9724953b2f Rename local var to enableRedisHttpSessionAnnotation
From enableWebSecurityAnnotation to enableRedisHttpSessionAnnotation.

Fixes gh-194
2015-07-25 07:44:21 -05:00
izeye
1266fd052e Remove duplicate var headers = {};
Fixes gh-197
2015-07-25 07:37:08 -05:00
Adib Saikali
6c970efb09 Fix <type> to </type> in Maven section of guides
Fix typo in maven dependiecs of some of the the guides
Cutting and pasting the maven dependecies from the guides was failing
due to an xml element that was not closed.

Fixes gh-211
2015-07-25 07:34:22 -05:00
Jean-Pierre Bergamin
a32ee3935d Add projects subdomain in project page URL
While the projects subdomain is not necessary due to a redirect
it does provide a faster experience for users.

This commit adds the projects subdomain to the project URL to
avoid unnecessary redirects.

Fixes gh-220
2015-07-25 07:33:43 -05:00
Slavisa Avramovic
cc0c6bda42 Fix Hazelcast Sample attributeValue -> attributeName
The id attribute should have been attributeName instead of
attributeValue

Fixes gh-221
2015-07-24 17:02:25 -05:00
Rob Winch
1bee69ef15 Add tests for SessionRepositoryFilter set HttpSessionStrategy null
Issue gh-224
2015-07-24 16:53:00 -05:00
james
3b0dd129ba Fix SessionRepository setHttpSessionStrategy Validationi
Fixes gh-224
2015-07-24 16:52:23 -05:00
Geoffrey Tucker
c3c6215a24 Remove EnableRedisKeyspaceNotificationsInitializer.CONFIG_NOTIFY_KEYSPACE_EVENTS
There's a duplicate of this field in
org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction which is
what is actually used.

Fixes gh-225
2015-07-24 16:42:05 -05:00
Rob Winch
8eca5ea4c6 Remove Embedded Redis
Fixes gh-248
2015-07-24 15:40:55 -05:00
Rob Winch
e4c74ebe91 Add MockMvc tests to rest sample 2015-07-09 15:12:22 -05:00
Andy Wilkinson
141aba9526 Use the latest version of the Spring IO Plugin
Spring IO Platform 2.0 will remove the managed versions .properties
file as support for it has been removed in Spring Boot 1.3.

This commit moves the build onto a new version of the Spring IO Plugin
that uses the Maven bom rather than the properties file.
2015-07-08 08:33:15 -05:00
Rob Winch
f5bb381191 Merge pull request #195 from manderson23/hazelcast
Replace "a Redis instance" with "Hazelcast" in index.jsp
2015-04-20 15:08:59 -05:00
Mark Anderson
b9151c6908 Replace "a Redis instance" with "Hazelcast" in index.jsp for Hazelcast sample. 2015-04-19 20:31:14 +01:00
Rob Winch
664b5aae21 Add EnableEmbeddedRedis to WebSocket Sample
This issue is a result of moving EmbeddedRedisConfiguration to its own
package in 865e381c7c.

Now we explicitly add EnableEmbeddedRedis to the configuration.

Fixes gh-191
2015-04-16 16:59:35 -05:00
Spring Buildmaster
b44b7365b5 Next development version 2015-04-15 13:43:24 -07:00
Rob Winch
23afc1b354 Fix RedisSessionExpirationPolicy to properly cleanup expired sessions
Previously forcibly cleaning up sessions was not working. All the cleanup
was done by Redis expiration. This meant that sessions would be kept alive
until Redis cleaned them up (non deterministic).

This commit resolves the mapping of expiration to session ids.

Fixes gh-169
2015-04-15 15:25:50 -05:00
Rob Winch
f711876347 Add test assert SessionRepository not invoked unless neededwq 2015-04-15 12:45:54 -05:00
Rob Winch
5879d15692 Valid HTTP session no longer invalidated when HttpSession not accessed
Fixes gh-188
2015-04-15 12:33:34 -05:00
Rob Winch
30d4ea996b Fix doc dependencies for gh-124 2015-04-14 22:46:02 -05:00
Rob Winch
d2e5005fa0 Allow disabling configuration of Redis
Fixes gh-124
2015-04-14 22:34:01 -05:00
Rob Winch
865e381c7c Move Embedded Redis classes to org.springframework.session.redis.embedded
This prevents the embedded redis from being picked up in ComponentScan
in any of the samples unless the @EnableEmebeddedRedis annotation is used.

Polish gh-184
2015-04-14 21:59:19 -05:00
Rob Winch
8c1f1c4c52 Update to embedded Redis 0.6
Fixes gh-187
2015-04-14 15:32:57 -05:00
Rob Winch
c2414c498f Polish httpsession.adoc 2015-04-10 13:47:53 -05:00
Rob Winch
4f8588b4bf Add HttpSession XML Guide
Fixes gh-139
2015-04-10 13:47:37 -05:00
Rob Winch
98cb22c670 Fix broken imports
Issues gh-184
2015-04-09 21:11:19 -05:00
Rob Winch
4a0aa6e608 Add EnableEmbeddedRedis
Fixes gh-184
2015-04-09 17:02:56 -05:00
Rob Winch
88151b4287 Fix rest sample tests
- move to itest folder
- dynamically allocate the port for Redis

Issues gh-183
2015-04-09 14:28:12 -05:00
Rob Winch
3a42147d3e Add NullRequestCache to rest sample
Fixes gh-183
2015-04-08 16:59:38 -05:00
Rob Winch
4631b57531 Make Servlet 2.5 compatible
Fixes gh-111 gh-182
2015-04-07 22:12:40 -05:00
Rob Winch
5417e59a50 Test against latest Spring IO snapshot
Fixes gh-181
2015-04-07 16:33:53 -05:00
Rob Winch
9ac570cba7 Update to Spring IO version
- Update Spring Boot 1.2.3.RELEASE
- Update Spring Security 4.0.0.RELEASE
- Update Spring 4.1.6.RELEASE
- Update Groovy 2.3.8
- Update Jackson 2.4.5
- Update Jedis 2.5.2
- Update Spring Data Redis 1.4.2

Fixes gh-126
2015-04-07 16:30:57 -05:00
Rob Winch
3d1cd9fae4 Implement changeSessionId()
Fixes gh-152
2015-04-03 17:07:21 -05:00
Rob Winch
3c72828500 SessionRepositoryFilterTests better setup of next request 2015-04-03 16:11:41 -05:00
Rob Winch
d3ab764c76 Update Boot sample to use Redis Starter
Fixes gh-125
2015-04-03 13:15:23 -05:00
Rob Winch
eddb10d52c Make RedisHttpSessionConfiguration more XML friendly
* add setMaxInactiveIntervalInSeconds
* add default value for maxInactiveIntervalInSeconds

Fixes gh-105
2015-04-03 13:00:27 -05:00
Rob Winch
208493b6fd Update to embedded 0.5
Fixes gh-179
2015-04-03 12:07:49 -05:00
Rob Winch
d46bc636de Add comment to end::how-does-it-work[]
Fixes gh-145
2015-04-03 11:13:13 -05:00
Rob Winch
4dedb4d10a Spaces to tabs and license cleanup 2015-04-03 11:13:13 -05:00
Rob Winch
93b8856a20 Merge pull request #178 from Eyads/patch-1
Fix typo in documentation

You can the basic steps -> You can follow the basic steps
2015-03-31 10:23:37 -05:00
Eyad Salamin
07e1c91f1c typo
You can the basic steps -> You can follow the basic steps
2015-03-31 09:41:51 +04:00
Rob Winch
2d8664d841 OnCommittedResponseWrapper tracks getOutputStream().write(byte[]) properly
Fixes gh-171
2015-03-10 00:29:26 -05:00
Rob Winch
72227b8d83 Merge pull request #157 from domdorn/javadoc_fixes
fixed javadoc errors
2015-02-23 10:28:28 -06:00
Rob Winch
34e2faa692 Merge pull request #156 from domdorn/patch-1
Upgrade to embedded-redis:0.4 (fixes #113)
2015-02-23 10:17:43 -06:00
Dominik Dorn
6347c32f7b fixed javadoc errors 2015-02-23 12:45:41 +01:00
Dominik Dorn
a7f686af73 Upgrade to embedded-redis:0.4 (fixes #113)
Embedded-Redis 0.4 got released; it now includes the latest versions of redis for win/mac/unix and 
therefor fixes the problems with 
ERR Unsupported CONFIG parameter: notify-keyspace-events #113
2015-02-23 12:09:12 +01:00
Rob Winch
020fbdaf4f Polish gh-154 2015-02-19 17:06:25 -06:00
Rob Winch
369e98c7ef SessionRepositoryRequestWrapper overrides isRequestedSessionIdValid
Fixes gh-142, gh-153
2015-02-19 16:49:38 -06:00
Dave Syer
29ad238307 Invoke HttpSessionStrategy.onNewSession if session id changed
Fixes gh-154
2015-02-19 16:49:38 -06:00
Rob Winch
ae03fac468 Merge pull request #123 from danveloper/master
pump spring-data-redis version
2015-01-26 12:47:30 -06:00
Daniel Woods
9534f9ee3a pump spring-data-redis version 2015-01-25 11:23:51 -08:00
Rob Winch
a25da734c1 Merge pull request #117 from brixtonasias/master
Small documentation improvements
2015-01-20 10:09:54 -06:00
Stefan Kohler
0ac460563f Make 'Filter' look more code-like 2015-01-20 17:03:22 +01:00
Stefan Kohler
8dd885a6c7 Add missing verb in note in the RESTful guide 2015-01-20 16:41:50 +01:00
Stefan Kohler
4926606841 Remove arbitrary verb 2015-01-20 16:40:30 +01:00
Stefan Kohler
d4d8a4e6c9 Reword after a word too much was found
Removed 'a' after the if and reworded sentence
2015-01-20 16:34:15 +01:00
Stefan Kohler
61a2586aed Add missing 'an' 2015-01-20 16:31:56 +01:00
Rob Winch
3be13ac10b Merge pull request #116 from brixtonasias/patch-1
Change gradlew run command to bootRun
2015-01-20 08:59:12 -06:00
Stefan Kohler
8dabbd26e5 Change gradlew run command to bootRun
In contrast to :tomcatRun :bootRun works for me.

For :tomcatRun I was getting: Task 'tomcatRun' not found in project ':samples:boot'.
2015-01-20 15:48:09 +01:00
Rob Winch
701f20cf04 Add named anchors to boot reference 2015-01-15 11:11:50 -06:00
Rob Winch
3f23643121 Polish Boot documentation
This removes the warnings produced by the invalid callout in the boot
reference. It also provides an example of how to customize the redis
connection and links to the "Connection to Redis" section of the
Spring Boot reference.

Issue #108
2015-01-15 11:11:33 -06:00
Roy Kachouh
37aafc5148 Update Spring boot sample documentation. 2015-01-15 10:44:12 -06:00
Roy Kachouh
afc3752b33 Remove unnecessary JedisConnectionFactory bean 2015-01-15 10:44:05 -06:00
Rob Winch
cf1a4b83bf Polish WebSocket doc cleanup
The callouts in the websocket guide need to be after the sample otherwise
the following warning is omitted:
asciidoctor: WARNING: guides/websocket.adoc: line 36: no callouts refer to list item 1
asciidoctor: WARNING: guides/websocket.adoc: line 38: no callouts refer to list item 2
asciidoctor: WARNING: websocket.adoc: line 36: no callouts refer to list item 1
asciidoctor: WARNING: websocket.adoc: line 38: no callouts refer to list item 2

This issue resolves the WARNING logs created due to the callout being
placed in the wrong location.

Issue #99
2015-01-15 10:41:54 -06:00
Rob Winch
ba07c1d562 Add httpsession-xml
Fixes #103
2015-01-14 11:40:49 -06:00
Rob Winch
f5e3deafa9 Merge pull request #99 from rstoyanchev/docs
Small improvement to WebSocket guide
2015-01-14 09:52:13 -06:00
Rossen Stoyanchev
43b393fb3f Small improvement to WebSocket guide
Simply move content around so that we first show what needs to be
done and then explain what is done internally. Arguably this is an
easier way to digest since the former is required knowledge while
the latter is optional.
2015-01-09 09:30:05 -05:00
Rob Winch
b5f96c3564 Fix REST guide formatting
A missing - caused the end of the REST guide to be escaped.

Fixes gh-96
2015-01-08 14:10:36 -06:00
Rob Winch
00d30f3dfd Next Development Version 2015-01-08 11:21:57 -06:00
Spring Buildmaster
97a05cf95d Next development version 2015-01-08 08:56:21 -08:00
Spring Buildmaster
1c829ae78a Release version 1.0.0.RELEASE 2015-01-08 08:56:16 -08:00
Rob Winch
480d193b19 Disable deploy of docs.jar 2015-01-08 10:45:00 -06:00
Rob Winch
d96c8f2ce5 Clarify Redis expirations and cleanup task 2015-01-08 10:29:07 -06:00
Rob Winch
57c361b878 Fix poms to be Maven Central compliant 2015-01-07 15:35:11 -06:00
Rob Winch
d62d5a8731 Disable Artifactory Deploy for WebSocket Sample
Fixes gh-95
2015-01-07 15:34:52 -06:00
Rob Winch
4fd62ae0af Disable Artifactory Deploy for Spring Boot Sample
Fixes gh-94
2015-01-07 15:34:30 -06:00
Rob Winch
3be96d903b Revert "Next development version"
This reverts commit ae722375d8.
2015-01-07 15:20:32 -06:00
Spring Buildmaster
ae722375d8 Next development version 2015-01-07 12:58:57 -08:00
Rob Winch
5eb3695967 Fix Javadoc formatting for RedisOperationsSessionRepository 2015-01-07 14:39:26 -06:00
Rob Winch
5847801a32 RedisSessionExpirationPolicy.cleanExpiredSessions does not delete performs get
The Problem:

The background task that cleans up sessions can incorrectly remove a
session due to a race condition.

Assume an existing session with the id "1" exists and will expire at
1420656360000. This means our redis store has the following:

spring:session:expirations:1420656360000 -> [1]
spring:session:session:1 -> <session>
Consider the following sequence:

* Thread 1 requests Session 1 and determines it should be forcibly deleted
up at 1420656420000
* Thread 2 requests Session 2 and determines it should be forcibly deleted
one minute later at 1420656480000
* Thread 2 removes Session 1 from 1420656360000, so it will no longer be
forcibly deleted at that time

spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>

* Thread 2 adds Session 1 to 1420656480000

spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>
spring:session:expirations:1420656480000 -> [1]

* Thread 1 removes Session 1 (which was already removed) from 1420656360000 (the original expiration)

spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>
spring:session:expirations:1420656480000 -> [1]

* Thread 1 adds Session 1 to 1420656420000

spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>
spring:session:expirations:1420656480000 -> [1]
spring:session:expirations:1420656420000 -> [1]

Now the session is mapped to be forcibly deleted in two locations.
However, at most it will be cleaned up in one location. This means that the
session will be forcibly deleted even if the session is continued to be
used.

Fixing the Issue:

Instead of deleting the session, we should have the background task access
the key which will only forcibly delete the key if it is expired. This mean
s that a session could at earliest be deleted when the value in the
datastore indicates.

This still means that a session can be deleted too soon since the
incorrect TTL may be set on a key. However, at worst this is the the
longest HTTP request length. Short of using distributed locking there
isn't a good answer to get exact consistency.

Fixes gh-93
2015-01-07 14:39:10 -06:00
Rob Winch
9b168423b8 Remove unnecessary @Override 2015-01-07 14:33:53 -06:00
Rob Winch
6904678382 Remove default port from iTests 2015-01-07 10:50:43 -06:00
Rob Winch
0f09d5e000 Polish EnableRedisHttpSessionExpireSessionDestroyedTests 2015-01-07 10:28:04 -06:00
Rob Winch
1eca2bc0ad Add Basic REST documentation to reference 2015-01-06 16:16:18 -06:00
Rob Winch
f619cef67a Remove Maven from referece documentation
Save details like Maven documentation for the in-depth guides.
2015-01-06 15:36:19 -06:00
Rob Winch
0f034312a2 Remove @Override from interface overrides 2015-01-06 13:47:49 -06:00
Rob Winch
090d106376 Polish docs 2015-01-06 13:46:36 -06:00
Rob Winch
cdf52507c0 Add the Spring IO plugin to spring-session and spring-session-data-redis
Fixes #62
2015-01-05 16:27:24 -06:00
Rob Winch
8acaed750a Polish doc 2015-01-05 16:13:18 -06:00
Rob Winch
79569f5a6f Polish doc 2015-01-05 16:12:45 -06:00
Rob Winch
ed28eab835 Added link to Javadoc in reference
Issue gh-41
2015-01-05 15:37:49 -06:00
Rob Winch
37572113c3 Fix links to samples
Issues gh-41
2015-01-05 15:33:57 -06:00
Rob Winch
1e56874338 RedisOperationsSessionRepository doesn't expose ExpirationRedisOperations
Fixes gh-91
2015-01-05 14:50:05 -06:00
Rob Winch
d2639570bb Update README to refer to reference documentation
Issue gh-41
2015-01-05 14:24:13 -06:00
Rob Winch
f4b87b7ae7 Include Time Unit for ExpiringSession maxinactiveInterval
Fixes gh-82
2015-01-05 14:10:04 -06:00
Rob Winch
755754e173 Add Javadoc
Issue gh-41
2015-01-05 14:06:28 -06:00
Rob Winch
62bf5fa11d Move Docs to reference folder
This makes the project align better with other Spring Projects.

Issue gh-41
2015-01-05 12:57:27 -06:00
Rob Winch
cd85c647ff Add REST Sample
Issue gh-41
2015-01-05 12:56:57 -06:00
Rob Winch
64a0312bf7 Start Documentation
Issue gh-41
2014-12-23 17:26:27 -06:00
Rob Winch
59dbd75ca2 Add REST sample
Fixes gh-89
2014-12-23 16:42:30 -06:00
Rob Winch
ddff1f4c0d Ensure values of _s are encoded
Fixes gh-80
2014-12-23 16:39:18 -06:00
Rob Winch
375db97f1c Move Java Versions to gradle.properties 2014-12-23 16:39:04 -06:00
Rob Winch
8f3600c49a samples/web->samples/httpsession 2014-12-23 16:39:04 -06:00
Rob Winch
7f9b5c0515 Ensure Redis Configured to Send Keyspace Notifications
Previously there was a possibility that Session to WebSocket mapping was
leaked if keyspace notifications were not enabled in Redis.

To resolve this the RedisHttpSessionConfiguration now ensures that Redis
is configured to enable Keyspace notifications.

Fixes gh-76 gh-81
2014-12-23 16:38:42 -06:00
Rob Winch
b3130edd98 Remove @Override from Interfaces
This is necessary to ensure the code compiles in Eclipse.

Fixes gh-83
2014-12-22 16:37:53 -06:00
Christopher Smith
873f4bacd6 Fix Javadoc for JDK8
Fixes gh-84
2014-12-22 16:37:33 -06:00
Rob Winch
900b9174d3 Fix integration tests for JDK8
Update jacoco version.

Fixes gh-86
2014-12-22 16:37:06 -06:00
Rob Winch
dd4ea20656 Add Spring Security Sample
Fixes gh-68
2014-12-12 16:09:11 -06:00
Rob Winch
0b403c3313 WebSocketRegistryListener handles null Session in SessionDisconnectEvent
Fixes gh-77
2014-12-12 15:26:07 -06:00
Rob Winch
c7e9c698a5 Update Versions 2014-12-12 12:56:19 -06:00
Rob Winch
8e80347022 Added Spring Boot Sample
Fixes gh-37
2014-12-12 12:56:19 -06:00
Rob Winch
977d1f474b Add Default Expiration Option to MapSessionRepository
Fixes gh-73
2014-12-11 21:00:03 -06:00
Rob Winch
646682b056 EnableWebMvcSecurity->EnableWebSecurity 2014-12-11 20:56:24 -06:00
Rob Winch
62ce30fb38 Remove @Override from interface method implementations
Issues: gh-71
2014-12-11 11:07:40 -06:00
Rob Winch
604ec4a50d Session.getAttribute returns generic type
This prevents the need to cast the attribute values.

Fixes: gh-64
2014-12-11 11:05:33 -06:00
Rob Winch
9834e91c6d Make JDK 1.5 compatible
Fixes gh-71
2014-11-26 08:49:58 -06:00
Rob Winch
3254bc2bef Update README Dependency Management Section
- Use pom
- Update versions
- Use aggregate pom for release
- Polish repository name
2014-11-26 08:48:50 -06:00
Rob Winch
5caf045ca0 Update to Gradle 2.2.1 2014-11-25 21:11:34 -06:00
Rob Winch
71cac12479 WebSocketSecurityConfig now uses .authenticated()
This is a simpler example without being coupled to any specific roles.
2014-11-25 20:38:07 -06:00
Rob Winch
d015f4c25a Added Hazelcast Sample
Fixes gh-63
2014-11-17 16:53:05 -06:00
Rob Winch
f7a35e8da3 Remove dependency on Spring for MapSessionRepository 2014-11-17 14:58:12 -06:00
Rob Winch
519c83ea5a Polish users log in error style 2014-11-17 14:51:53 -06:00
Rob Winch
3cd4364566 Add Invalid Username/Password to users sample
gh-49
2014-11-17 14:48:58 -06:00
Rob Winch
495d19982d Update CookieHttpSessionStrategy default parameter from u to _s
This both provides two things:
- more meaningful parameter name  since it relates to session
- less likely to have a collission due to the _ prefix

gh-49
2014-11-17 13:26:47 -06:00
Rob Winch
6b6ed48095 users sample password type=text->type=password
Change the password input of the users sample from type of text to type
password.

gh-49
2014-11-17 12:59:42 -06:00
Rob Winch
a0805e8411 Add support for multiple sessions in a browser
Fixes gh-49
2014-11-16 22:13:27 -06:00
Rob Winch
9df0594573 Polish RedisOperationsSessionRepository ConnectionFactory Constructor
Add test and remove unnecessary comment.

Relates to gh-61
2014-11-14 21:12:41 -06:00
Steve Storey
0926df9c54 Force initialization of default values after creation of the default template 2014-11-14 21:10:56 -06:00
Rob Winch
b001580334 Removed path check when reading Cookie
The Cookie path is not passed to the server so there is no point to check
it.

Relates to gh-34
2014-11-12 21:37:02 -06:00
Rob Winch
25804f1725 Add WebSocket Support
Fixes gh-35
2014-11-12 21:13:23 -06:00
Rob Winch
04fcc393fd MapSessionRepository handles expried sessions
Fixes gh-31
2014-11-12 17:37:37 -06:00
Rob Winch
46173f6486 Add SessionDestroyedEvent
Add support for SessionDestroyedEvent so that applications can detect
when a Spring Session has expired or explicitly deleted. This will
ensure that we can cleanup resources (i.e. WebSockets) can be cleaned up
when a Session ends.

Fixes gh-60
2014-11-12 17:37:30 -06:00
Rob Winch
6430e43f0e Add Support for background task cleanup of RedisSession
Redis key expiration has no guarantees of when the expired key is
actually removed. In some instances, it is necessary to clean up resources
as soon as the sessione expires. For example, when an HTTP Session expires
we must ensure that the associated Web Socket sessions are closed.

Sessions are now mapped to their expiration times and a background task
is used to clean up the sessions as they expire.

Fixes gh-59
2014-11-12 17:30:50 -06:00
Rob Winch
dbc7018a11 Add ExpiringSession.isExpired
Fixes gh-58
2014-11-12 17:29:09 -06:00
Rob Winch
9cb5c75b4e MapSessionTests tabs -> spaces 2014-11-12 17:27:39 -06:00
Rob Winch
1ad47fc7b1 Disable onCommit after first commit
At times OnCommittedResponseWrapper#onResponseCommitted() can be invoked
multiple times. For example, when flush is performed multiple times. This
means that the session can be written multiple times which is inefficient.
Instead, we should only save on the first update.

Fixes gh-57
2014-11-12 17:26:43 -06:00
Rob Winch
2a558885ad Polish SessionRepository javadoc
Clarifies that getSession updates expiration, but it must be saved for
the expiration to be persisted.
2014-11-10 13:39:24 -06:00
Rob Winch
0f8c3b8d07 Ignore .springBeans 2014-11-10 13:37:34 -06:00
Rob Winch
87e4dd8a0b Allow configure HttpSessionStrategy 2014-10-27 13:45:32 -05:00
Rob Winch
97f3333194 SessionRepositoryFilter implements Ordered
Support Spring Boot applications by implementing Ordered.
2014-10-27 11:28:37 -05:00
Rob Winch
f1ea4793bb Remove throws Exception from Sample config 2014-10-27 11:27:59 -05:00
Rob Winch
07ff3088bf Change RedisOperationsSessionRepository.BOUNDED_HASH_KEY_PREFIX
Previously the key contained spring-security-sessions in it since the
project was originally going to be a part of Spring Security.

This commit moves it to spring:session:sessions: to better align with
being part of Spring Session

Fixes #46
2014-10-08 22:01:25 -05:00
Rob Winch
6a22a11187 Allow configure expires for EnableRedisHttpSession 2014-10-01 16:14:40 -05:00
Rob Winch
b05fe2355e Move from default package to sample 2014-09-30 14:28:39 -05:00
Rob Winch
480296c185 Externalize the embedded Redis Server configuration
This provides a clear separation of what configuration does what.
2014-09-30 14:26:39 -05:00
Rob Winch
d026dd6821 Add AbstractHttpSessionApplicationInitializer
This reduces the boilerplate code required to register Spring Session with
the servlet container.

Fixes gh-43
2014-09-30 14:13:26 -05:00
Rob Winch
417174388b Add EnableRedisHttpSession
Fixes gh-42
2014-09-30 13:40:15 -05:00
Rob Winch
2ca482f060 Add spring-version to README 2014-09-30 13:22:17 -05:00
Rob Winch
750ae663fc Create spring-session-data-redis pom 2014-09-30 10:09:47 -05:00
Rob Winch
0abbba0309 Polish Generics and Update Spring Version
We needed to update Spring Version to avoid
https://jira.spring.io/browse/SPR-11471
2014-09-30 09:07:46 -05:00
Rob Winch
6130a10d14 Remove Compiler Warnings
* Fixes Generics
* Remove unused imports
* Ignore serialize warnings in tests

Fixes gh-40
2014-09-29 22:00:46 -05:00
Rob Winch
52827f4a46 Remove samples/web/.project 2014-09-29 21:20:58 -05:00
Rob Winch
e96e0e3dbb Add integration test folders to Eclipse 2014-09-29 21:18:51 -05:00
Rob Winch
876d466563 Remove JDK8 options from Javadoc 2014-09-29 21:04:06 -05:00
Rob Winch
83b902c86b Add ext.javadocLinks 2014-09-29 21:00:31 -05:00
Rob Winch
ee4bd71855 Cleanup Maven Publishing
* Ensure that Maven Source Jars are published
* Ensure that Javadoc is published
* Ensure Maven pom includes required information for central

Fixes gh-36
2014-09-29 20:56:54 -05:00
Rob Winch
dcfd85e933 Sample properly uses RedisTemplate<String,ExpiringSession>
Fixes #33
2014-08-11 22:36:47 -05:00
Rob Winch
a4e003ebf1 HeaderSessionStrategy uses response.setHeader
Previously multiple headers might be outputed. This ensures that only a
single header is sent back with the session id.

Fixes #32
2014-08-01 15:24:50 -05:00
Rob Winch
2732a183f3 OnCommitedResponseWrapper commits when buffer exceeded
Fixes #26
2014-08-01 15:24:50 -05:00
Rob Winch
0d217c680a OnCommittedResponseWrapper triggers commit when content length is reached
Fixes #26
2014-07-31 16:01:18 -05:00
Rob Winch
eb0da565f3 Reorder SessionRepository methods
Fixes #29
2014-07-30 12:10:56 -05:00
Rob Winch
903017285b Create ExpiringSession which extends Session
This provides a better separation for consumers of the API. Most users are
likely not interested in checking to see if a session is expired so they
can focus on the Session API.

Fixes #28
2014-07-30 12:10:56 -05:00
Rob Winch
f1fa380bdd Move session.web to session.web.http
Fixes #18
2014-07-30 12:10:56 -05:00
Rob Winch
699bdf94a0 Remove Sesion.setLastAccessedTime
Fixes #16
2014-07-30 12:10:56 -05:00
Rob Winch
cc71e1f567 Remove Serializable from Session
Fixes #15
2014-07-30 12:10:55 -05:00
Rob Winch
6708f6a5f4 Work around IDEA-126899 2014-07-30 12:10:55 -05:00
Rob Winch
923c00fd5f Merge pull request #14 from arafalov/patch-1
Spelling fix for word 'attribute'
2014-07-25 14:24:02 -05:00
Rob Winch
256043a3a1 Merge pull request #24 from tyutyutyu/patch-1
Typo in index.jsp
2014-07-25 14:23:13 -05:00
István Földházi
4aacf2fcea Typo in index.jsp 2014-07-21 09:58:21 +02:00
Alexandre Rafalovitch
c8c8844fff Spelling fix 2014-07-10 08:55:34 +07:00
Rob Winch
07021da671 Revert "Update to Gradle 2.0"
This reverts commit f271920936.
2014-07-09 09:50:43 -05:00
Rob Winch
f271920936 Update to Gradle 2.0 2014-07-09 09:26:13 -05:00
Rob Winch
f0482dba20 Update to propdeps 0.0.7 2014-07-09 09:26:01 -05:00
Rob Winch
8e553f5e4c Link to Benefits in Readme 2014-07-08 14:54:02 -05:00
Rob Winch
df280f956b Polish Updating Dependencies in README 2014-07-08 14:52:10 -05:00
Rob Winch
fb5bed1416 Next development version 2014-07-08 14:48:13 -05:00
478 changed files with 42853 additions and 4596 deletions

19
.editorconfig Normal file
View File

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

7
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

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

7
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

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

7
.gitignore vendored
View File

@@ -7,4 +7,9 @@ bin
.settings
.project
target
out
out
.springBeans
*.rdb
.checkstyle
!etc/eclipse/.checkstyle
!**/src/**/build

20
.travis.yml Normal file
View File

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

44
CODE_OF_CONDUCT.adoc Normal file
View File

@@ -0,0 +1,44 @@
= Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open
and welcoming community, we pledge to respect all people who contribute through reporting
issues, posting feature requests, updating documentation, submitting pull requests or
patches, and other activities.
We are committed to making participation in this project a harassment-free experience for
everyone, regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses,
without explicit permission
* Other unethical or unprofessional conduct
Project maintainers have the right and responsibility to remove, edit, or reject comments,
commits, code, wiki edits, issues, and other contributions that are not aligned to this
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
that they deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
consistently applying these principles to every aspect of managing this project. Project
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
from the project team.
This Code of Conduct applies both within project spaces and in public spaces when an
individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
be reviewed and investigated and will result in a response that is deemed necessary and
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
with regard to the reporter of an incident.
This Code of Conduct is adapted from the
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]

26
CONTRIBUTING.adoc Normal file
View File

@@ -0,0 +1,26 @@
= Contributing to Spring Session
Spring Session is released under the Apache 2.0 license. If you would like to contribute
something, or simply want to hack on the code this document should help you get started.
== Code of Conduct
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
== Using GitHub issues
We use GitHub issues to track bugs and enhancements. If you have a general usage question
please ask on https://stackoverflow.com[Stack Overflow]. The Spring Session team and the
broader community monitor the https://stackoverflow.com/tags/spring-session[`spring-session`]
tag.
If you are reporting a bug, please help to speed up problem diagnosis by providing as much
information as possible. Ideally, that would include a small sample project that
reproduces the problem.
== Sign the Contributor License Agreement
If you have not previously done so, please fill out and
submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].

179
Jenkinsfile vendored Normal file
View File

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

202
LICENSE.txt Normal file
View File

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

View File

@@ -1,191 +1,32 @@
= Spring Session
Rob Winch
1.0.0.BUILD-SNAPSHOT
:toc:
:toc-placement: preamble
:sectanchors:
:icons: font
:source-highlighter: prettify
:idseparator: -
:idprefix:
:doctype: book
:spring-session-version: 1.0.0.BUILD-SNAPSHOT
Spring Session aims to provide a common infrastructure for managing sessions. This allows for:
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"]
* Accessing a session from any environment (i.e. web, messaging infrastructure, etc)
* In a web environment
** Support for clustering in a vendor neutral way
** Pluggable strategy for determining the session id
** Easily keep the HttpSession alive when a WebSocket is active
Spring Session provides an API and implementations for managing a user's session information, while also making it trivial to support clustered sessions without being tied to an application container specific solution.
It also provides transparent integration with:
= Quick Start
* `HttpSession` - allows replacing the `HttpSession` in an application container (i.e. Tomcat) neutral way, with support for providing session IDs in headers to work with RESTful APIs.
* `WebSocket` - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
* `WebSession` - allows replacing the Spring WebFlux's `WebSession` in an application container neutral way.
This section describes how to use Spring Session to use Redis when interacting with a web application's HttpSession. If you'd like to skip the reading, you can also refer to the <<sample>>
== Modules
== Updating Dependencies
Before you use the project, you must ensure to update your dependencies. Instructions for building with Maven and Gradle have been provided below:
Spring Session consists of the following modules:
* <<building-with-maven>>
* <<building-with-gradle>>
* Spring Session Core - provides core Spring Session functionalities and APIs
* Spring Session Data Redis - provides `SessionRepository` and `ReactiveSessionRepository` implementation backed by Redis and configuration support
* Spring Session JDBC - provides `SessionRepository` implementation backed by a relational database and configuration support
* Spring Session Hazelcast - provides `SessionRepository` implementation backed by Hazelcast and configuration support
=== Building with Maven
== Code of Conduct
The project is available in the https://github.com/spring-projects/spring-framework/wiki/SpringSource-repository-FAQ[Spring Maven Repository]. If you are using Maven, you will want to make the following updates.
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
**Using the latest Snapshot in Maven**
== Spring Session Project Site
If you want the latest snapshot, ensure you have the following repository in your pom.xml:
You can find the documentation, issue management, support, samples, and guides for using Spring Session at https://projects.spring.io/spring-session/
[source,xml]
----
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
----
== License
Then ensure you have added the dependency:
[source,xml]
[subs="verbatim,attributes"]
----
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>{spring-session-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>{spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.2</version>
</dependency>
----
=== Building with Gradle
**Using the latest Snapshot in Gradle**
If you want the latest snapshot, ensure you have the following repository in your pom.xml:
[source,groovy]
----
repositories {
maven { url 'https://repo.spring.io/libs-snapshot' }
}
----
Then ensure you have added the dependency:
[source,groovy]
[subs="verbatim,attributes"]
----
dependencies {
compile "org.springframework.session:spring-session:{spring-session-version}",
"org.springframework:spring-web:{spring-version}",
"org.springframework.data:spring-data-redis:1.3.0.RELEASE",
"redis.clients:jedis:2.4.1",
"org.apache.commons:commons-pool2:2.2"
}
----
== Spring Configuration
Add the following Spring Configuration:
[source,java]
----
@Configuration
public class Config {
@Bean
public JedisConnectionFactory connectionFactory() throws Exception {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<String,Session> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Session> template = new RedisTemplate<String, Session>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, Session> redisTemplate) {
return new RedisOperationsSessionRepository(redisTemplate);
}
@Bean
public SessionRepositoryFilter sessionFilter(RedisOperationsSessionRepository sessionRepository) {
return new SessionRepositoryFilter(sessionRepository);
}
}
----
In our example, we are connecting to the default port (6379). For more information on configuring Spring Data Redis, refer to the http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
== Servlet Initialization
We next need to be sure our Servlet Container (i.e. Tomcat) is properly configured.
. First we need ensure that our `Config` class from above was loaded. In the example below we do this by extending `AbstractContextLoaderInitializer` and implementing `createRootApplicationContext`.
. Next we need to be sure the `SessionRepositoryFilter` is regsitered with the Servlet Container. We can do this by mapping a `DelegatingFilterProxy` to every request with the same name as the bean name of our `SessionRepositoryFilter`. In our instance, the bean name is the method name we used to create our `SessionRepositoryFilter`.
[source,java]
----
public class Initializer extends AbstractContextLoaderInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addFilter("sessionFilter", DelegatingFilterProxy.class)
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
}
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(Config.class);
return context;
}
}
----
= Sample
The code contains a https://github.com/spring-projects/spring-session/tree/master/samples/web[sample web application]. To run the sample:
. Obtain the source by https://github.com/spring-projects/spring-session[cloning the repository] or https://github.com/spring-projects/spring-session/archive/master.zip[downloading] it.
. Run the application using gradle
.. Linux / OSX `./gradlew tomcatRun`
.. Windows `.\gradlew.bat tomcatRun`
. Visit http://localhost:8080/
= Benefits
* This can make clustering much easier. This is nice because the clustering setup is done in a vendor neutral way. Furthermore, in some environments (i.e. PaaS solutions) developers cannot modify the cluster settings easily.
* We can use different strategies for determining the session id. This gives us at least a few benefits
** Allowing for a single browser to have multiple simultaneous sessions in a transparent fashion. For example, many developers wish to allow a user to authenticate with multiple accounts and switch between them similar to how you can in gmail.
** When using a REST API, the session can be specified using a header instead of the JSESSIONID cookie (which leaks implementation details to the client). Many would argue that session is bad in REST because it has state, but it is important to note that session is just a form of cache and used responsibly it will increase performance & security.
** When a session id is acquired in a header, we can default CSRF protection to off. This is because if the session id is found in the header we know that it is impossible to be a CSRF attack since, unlike cookies, headers must be manually populated.
* We can easily keep the HttpSession and WebSocket Session in sync. Imagine a web application like gmail where you can authenticate and either write emails (HTTP requests) or chat (WebSocket). In standard servlet environment there is no way to keep the HttpSession alive through the WebSocket so you must ping the server. With our own session strategy we can have the WebSocket messages automatically keep the HttpSession alive. We can also destroy both sessions at once easily.
* We can provide hooks to allow users to invalidate sessions that should not be active. For example, if you look in the lower right of gmail you can see the last account activity and click "Details". This shows a listing of all the active sessions along with the IP address, location, and browser information for your account.
** Users can look through this and determine if anything is suspicious (i.e. if their account has a session that is associated to a country they have never been) and invalidate that session and change their password.
** Another useful example is perhaps they checked their mail at the library and forgot to log out. With this custom mechanism this is very possible.
* Spring Security currently supports restricting the number of concurrent sessions each user can have. The implementation works, but does so passively since we cannot get a handle to the session from the session id. Specifically, each time a user requests a page we check to see if that session id is valid in a separate data store. If it is no longer valid, we invalidate the session. With this new mechanism we can invalidate the session from the session id.
Spring Session is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].

View File

@@ -1,35 +1,40 @@
buildscript {
repositories {
maven { url "https://repo.spring.io/plugins-release" }
}
dependencies {
classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3")
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.6")
classpath("org.springframework.build.gradle:spring-io-plugin:0.0.3.RELEASE")
classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1')
classpath('org.asciidoctor:asciidoctor-gradle-plugin:0.7.0')
classpath('org.asciidoctor:asciidoctor-java-integration:0.1.4.preview.1')
}
ext {
releaseBuild = version.endsWith('RELEASE')
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.2.0.M2'
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release/' }
}
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.25.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE'
}
}
apply plugin: 'io.spring.convention.root'
apply plugin: 'io.spring.nohttp'
repositories {
mavenCentral()
}
group = 'org.springframework.session'
description = 'Spring Session'
ext.JAVA_GRADLE = "$rootDir/gradle/java.gradle"
ext.TOMCAT_GRADLE = "$rootDir/gradle/tomcat.gradle"
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = JavaVersion.VERSION_1_8
apply plugin: 'sonar-runner'
sonarRunner {
sonarProperties {
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.projectName", "Spring Session"
property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
property "sonar.links.homepage", 'https://github.com/spring-projects/spring-session'
property "sonar.links.ci", 'https://build.spring.io/browse/SESSION'
property "sonar.links.issue", 'https://github.com/spring-projects/spring-session/issues'
property "sonar.links.scm", 'https://github.com/spring-projects/spring-session'
property "sonar.links.scm_dev", 'https://github.com/spring-projects/spring-session.git'
property "sonar.java.coveragePlugin", "jacoco"
}
}
tasks.withType(Test) {
useJUnitPlatform()
}
}
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
</module>
<module name="io.spring.javaformat.checkstyle.SpringChecks"/>
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
<property name="regexp" value="true"/>
<property name="illegalPkgs"
value="^sun.*, ^org\.apache\.commons\.(?!compress|dbcp2|lang|lang3|logging|pool2).*, ^com\.google\.common.*, ^org\.flywaydb\.core\.internal.*, ^org\.testcontainers\.shaded.*"/>
<property name="illegalClasses"
value="^reactor\.core\.support\.Assert, ^org\.junit\.rules\.ExpectedException, ^org\.slf4j\.LoggerFactory"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="maximum" value="0"/>
<property name="format" value="org\.junit\.Assert\.assert"/>
<property name="message" value="Please use AssertJ imports."/>
<property name="ignoreComments" value="true"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="maximum" value="0"/>
<property name="format"
value="assertThatExceptionOfType\((NullPointerException|IllegalArgumentException|IOException|IllegalStateException)\.class\)"/>
<property name="message" value="Please use specialized AssertJ assertThat*Exception method."/>
<property name="ignoreComments" value="true"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="maximum" value="0"/>
<property name="format" value="org\.mockito\.Mockito\.(when|doThrow|doAnswer)"/>
<property name="message" value="Please use BDDMockito imports."/>
<property name="ignoreComments" value="true"/>
</module>
</module>
</module>

View File

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

11
etc/eclipse/.checkstyle Normal file
View File

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

View File

@@ -0,0 +1,295 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12">
<profile kind="CodeFormatterProfile" name="Spring Session Java Conventions" version="12">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="90"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="90"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
version=1.0.0.M1
version=2.2.0.M2

View File

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

View File

@@ -1,76 +0,0 @@
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'javadocHotfix'
apply plugin: 'eclipse-wtp'
apply plugin: 'propdeps'
apply plugin: 'propdeps-idea'
apply plugin: 'propdeps-eclipse'
group = 'org.springframework.session'
ext.jstlVersion = '1.2.1'
ext.servletApiVersion = '3.0.1'
ext.springSecurityVersion = '3.2.4.RELEASE'
ext.springVersion = '4.0.2.RELEASE'
ext.groovyVersion = '2.0.5'
ext.seleniumVersion = '2.33.0'
ext.spockVersion = '0.7-groovy-2.0'
ext.gebVersion = '0.9.0'
ext.spockDependencies = [
dependencies.create("org.spockframework:spock-core:$spockVersion") {
exclude group: 'junit', module: 'junit-dep'
}
]
ext.gebDependencies = spockDependencies + [
"org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion",
"org.gebish:geb-spock:$gebVersion",
'commons-httpclient:commons-httpclient:3.1',
"org.codehaus.groovy:groovy:$groovyVersion"
]
ext.jstlDependencies = [
"javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:$jstlVersion",
"org.apache.taglibs:taglibs-standard-jstlel:1.2.1"
]
repositories {
mavenCentral()
maven { url 'http://clojars.org/repo' }
}
// Integration test setup
configurations {
integrationTestCompile {
extendsFrom testCompile, optional, provided
}
integrationTestRuntime {
extendsFrom integrationTestCompile, testRuntime
}
}
sourceSets {
integrationTest {
java.srcDir file('src/integration-test/java')
groovy.srcDirs file('src/integration-test/groovy')
resources.srcDir file('src/integration-test/resources')
compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integrationTestCompile
runtimeClasspath = output + compileClasspath + configurations.integrationTestRuntime
}
}
task integrationTest(type: Test, dependsOn: jar) {
testClassesDir = sourceSets.integrationTest.output.classesDir
logging.captureStandardOutput(LogLevel.INFO)
classpath = sourceSets.integrationTest.runtimeClasspath
maxParallelForks = 1
reports {
html.destination = project.file("$project.buildDir/reports/integration-tests/")
junitXml.destination = project.file("$project.buildDir/integration-test-results/")
}
}
project.idea.module {
scopes.TEST.plus += [project.configurations.integrationTestRuntime]
}

View File

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

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Wed Jun 18 14:02:09 CDT 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-1.12-all.zip

126
gradlew vendored
View File

@@ -1,4 +1,20 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
@@ -6,47 +22,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +36,49 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -90,7 +105,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +129,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +170,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

30
gradlew.bat vendored
View File

@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,14 +24,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +62,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +75,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>samples/web</name>
<comment/>
<projects/>
<natures>
<nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
</natures>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<linkedResources/>
</projectDescription>

View File

@@ -1,23 +0,0 @@
apply from: JAVA_GRADLE
apply from: TOMCAT_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false
sonarRunner {
skipProject = true
}
dependencies {
compile project(':spring-session'),
"org.springframework:spring-web:$springVersion",
"org.springframework.data:spring-data-redis:1.3.0.RELEASE",
"redis.clients:jedis:2.4.1",
"org.apache.commons:commons-pool2:2.2",
"redis.embedded:embedded-redis:0.2",
jstlDependencies
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
testCompile 'junit:junit:4.11'
integrationTestCompile gebDependencies
}

View File

@@ -1,28 +0,0 @@
import geb.spock.*
import spock.lang.Stepwise
import pages.*
/**
* Tests the CAS sample application using service tickets.
*
* @author Rob Winch
*/
@Stepwise
class AttributeTests extends GebReportingSpec {
def 'first visit no attributes'() {
when:
to HomePage
then:
attributes.empty
}
def 'create attribute'() {
when:
createAttribute('a','b')
then:
attributes.size() == 1
attributes[0].name == 'a'
attributes[0].value == 'b'
}
}

View File

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

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.Session;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.web.SessionRepositoryFilter;
import redis.clients.jedis.Protocol;
import redis.embedded.RedisServer;
/**
* @author Rob Winch
*/
@Configuration
public class Config {
@Bean
public RedisServerBean redisServer() {
return new RedisServerBean();
}
class RedisServerBean implements InitializingBean, DisposableBean {
private RedisServer redisServer;
@Override
public void afterPropertiesSet() throws Exception {
redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start();
}
@Override
public void destroy() throws Exception {
if(redisServer != null) {
redisServer.stop();
}
}
}
@Bean
public JedisConnectionFactory connectionFactory() throws Exception {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<String,Session> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Session> template = new RedisTemplate<String, Session>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, Session> redisTemplate) {
return new RedisOperationsSessionRepository(redisTemplate);
}
@Bean
public SessionRepositoryFilter sessionFilter(RedisOperationsSessionRepository sessionRepository) {
return new SessionRepositoryFilter(sessionRepository);
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.util.EnumSet;
/**
* @author Rob Winch
*/
public class Initializer extends AbstractContextLoaderInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addFilter("sessionFilter", DelegatingFilterProxy.class)
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
}
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(Config.class);
return context;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,16 @@
rootProject.name = 'spring-session-build'
include 'spring-session'
include 'samples/web'
include 'spring-session-core'
include 'spring-session-data-redis'
include 'spring-session-docs'
include 'spring-session-hazelcast'
include 'spring-session-jdbc'
file('spring-session-samples').eachDirMatch(~/spring-session-sample-.*/) { dir ->
include dir.name
project(":$dir.name").projectDir = dir
}
rootProject.children.each { project ->
project.buildFileName = "${project.name}.gradle"
}

View File

@@ -0,0 +1,29 @@
apply plugin: 'io.spring.convention.spring-module'
description = "Spring Session"
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"
optional "org.springframework:spring-messaging"
optional "org.springframework:spring-web"
optional "org.springframework:spring-webflux"
optional "org.springframework:spring-websocket"
optional "org.springframework.security:spring-security-core"
optional "org.springframework.security:spring-security-web"
testCompile "io.projectreactor:reactor-test"
testCompile "org.mockito:mockito-core"
testCompile "edu.umd.cs.mtc:multithreadedtc"
testCompile "org.springframework:spring-test"
testCompile "org.assertj:assertj-core"
testCompile "org.springframework.security:spring-security-core"
testCompile "org.junit.jupiter:junit-jupiter-api"
testCompile "org.junit.jupiter:junit-jupiter-params"
testRuntime "org.junit.jupiter:junit-jupiter-engine"
}

View File

@@ -0,0 +1,75 @@
/*
* 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;
import java.util.Map;
/**
* Extends a basic {@link SessionRepository} to allow finding sessions by the specified
* index name and index value.
*
* @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.
*
* @since 1.1
*/
String PRINCIPAL_NAME_INDEX_NAME = FindByIndexNameSessionRepository.class.getName()
.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.
*
* @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.
*/
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

@@ -0,0 +1,230 @@
/*
* 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;
import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* <p>
* A {@link Session} implementation that is backed by a {@link java.util.Map}. The
* defaults for the properties are:
* </p>
* <ul>
* <li>id - a secure random generated id</li>
* <li>creationTime - the moment the {@link MapSession} was instantiated</li>
* <li>lastAccessedTime - the moment the {@link MapSession} was instantiated</li>
* <li>maxInactiveInterval - 30 minutes</li>
* </ul>
*
* <p>
* This implementation has no synchronization, so it is best to use the copy constructor
* when working on multiple threads.
* </p>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public final class MapSession implements Session, Serializable {
/**
* Default {@link #setMaxInactiveInterval(Duration)} (30 minutes).
*/
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
private String id;
private final String originalId;
private Map<String, Object> sessionAttrs = new HashMap<>();
private Instant creationTime = Instant.now();
private Instant lastAccessedTime = this.creationTime;
/**
* Defaults to 30 minutes.
*/
private Duration maxInactiveInterval = Duration.ofSeconds(DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
/**
* Creates a new instance with a secure randomly generated identifier.
*/
public MapSession() {
this(generateId());
}
/**
* Creates a new instance with the specified id. This is preferred to the default
* constructor when the id is known to prevent unnecessary consumption on entropy
* which can be slow.
*
* @param id the identifier to use
*/
public MapSession(String id) {
this.id = id;
this.originalId = id;
}
/**
* Creates a new instance from the provided {@link Session}.
*
* @param session the {@link Session} to initialize this {@link Session} with. Cannot
* be null.
*/
public MapSession(Session session) {
if (session == null) {
throw new IllegalArgumentException("session cannot be null");
}
this.id = session.getId();
this.originalId = this.id;
this.sessionAttrs = new HashMap<>(
session.getAttributeNames().size());
for (String attrName : session.getAttributeNames()) {
Object attrValue = session.getAttribute(attrName);
if (attrValue != null) {
this.sessionAttrs.put(attrName, attrValue);
}
}
this.lastAccessedTime = session.getLastAccessedTime();
this.creationTime = session.getCreationTime();
this.maxInactiveInterval = session.getMaxInactiveInterval();
}
@Override
public void setLastAccessedTime(Instant lastAccessedTime) {
this.lastAccessedTime = lastAccessedTime;
}
@Override
public Instant getCreationTime() {
return this.creationTime;
}
@Override
public String getId() {
return this.id;
}
/**
* Get the original session id.
* @return the original session id
* @see #changeSessionId()
*/
public String getOriginalId() {
return this.originalId;
}
@Override
public String changeSessionId() {
String changedId = generateId();
setId(changedId);
return changedId;
}
@Override
public Instant getLastAccessedTime() {
return this.lastAccessedTime;
}
@Override
public void setMaxInactiveInterval(Duration interval) {
this.maxInactiveInterval = interval;
}
@Override
public Duration getMaxInactiveInterval() {
return this.maxInactiveInterval;
}
@Override
public boolean isExpired() {
return isExpired(Instant.now());
}
boolean isExpired(Instant now) {
if (this.maxInactiveInterval.isNegative()) {
return false;
}
return now.minus(this.maxInactiveInterval).compareTo(this.lastAccessedTime) >= 0;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAttribute(String attributeName) {
return (T) this.sessionAttrs.get(attributeName);
}
@Override
public Set<String> getAttributeNames() {
return new HashSet<>(this.sessionAttrs.keySet());
}
@Override
public void setAttribute(String attributeName, Object attributeValue) {
if (attributeValue == null) {
removeAttribute(attributeName);
}
else {
this.sessionAttrs.put(attributeName, attributeValue);
}
}
@Override
public void removeAttribute(String attributeName) {
this.sessionAttrs.remove(attributeName);
}
/**
* Sets the time that this {@link Session} was created. The default is when the
* {@link Session} was instantiated.
* @param creationTime the time that this {@link Session} was created.
*/
public void setCreationTime(Instant creationTime) {
this.creationTime = creationTime;
}
/**
* Sets the identifier for this {@link Session}. The id should be a secure random
* generated value to prevent malicious users from guessing this value. The default is
* a secure random generated identifier.
*
* @param id the identifier for this session.
*/
public void setId(String id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Session && this.id.equals(((Session) obj).getId());
}
@Override
public int hashCode() {
return this.id.hashCode();
}
private static String generateId() {
return UUID.randomUUID().toString();
}
private static final long serialVersionUID = 7160779239673823561L;
}

View File

@@ -0,0 +1,108 @@
/*
* 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;
import java.time.Duration;
import java.util.Map;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
/**
* A {@link SessionRepository} backed by a {@link java.util.Map} and that uses a
* {@link MapSession}. The injected {@link java.util.Map} can be backed by a distributed
* NoSQL store like Hazelcast, for instance. Note that the supplied map itself is
* responsible for purging the expired sessions.
*
* <p>
* The implementation does NOT support firing {@link SessionDeletedEvent} or
* {@link SessionExpiredEvent}.
* </p>
*
* @author Rob Winch
* @since 1.0
*/
public class MapSessionRepository implements SessionRepository<MapSession> {
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private final Map<String, Session> sessions;
/**
* Creates a new instance backed by the provided {@link java.util.Map}. This allows
* injecting a distributed {@link java.util.Map}.
*
* @param sessions the {@link java.util.Map} to use. Cannot be null.
*/
public MapSessionRepository(Map<String, Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = sessions;
}
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
* should be kept alive between client requests.
*/
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
@Override
public void save(MapSession session) {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
}
this.sessions.put(session.getId(), new MapSession(session));
}
@Override
public MapSession findById(String id) {
Session saved = this.sessions.get(id);
if (saved == null) {
return null;
}
if (saved.isExpired()) {
deleteById(saved.getId());
return null;
}
return new MapSession(saved);
}
@Override
public void deleteById(String id) {
this.sessions.remove(id);
}
@Override
public MapSession createSession() {
MapSession result = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
result.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return result;
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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;
import java.time.Duration;
import java.util.Map;
import reactor.core.publisher.Mono;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
/**
* A {@link ReactiveSessionRepository} backed by a {@link Map} and that uses a
* {@link MapSession}. The injected {@link java.util.Map} can be backed by a distributed
* NoSQL store like Hazelcast, for instance. Note that the supplied map must be a
* non-blocking map, and is itself responsible for purging the expired sessions.
*
* <p>
* The implementation does NOT support firing {@link SessionDeletedEvent} or
* {@link SessionExpiredEvent}.
* </p>
*
* @author Rob Winch
* @since 2.0
*/
public class ReactiveMapSessionRepository implements ReactiveSessionRepository<MapSession> {
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private final Map<String, Session> sessions;
/**
* Creates a new instance backed by the provided {@link Map}. This allows injecting a
* distributed {@link Map}.
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public ReactiveMapSessionRepository(Map<String, Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = sessions;
}
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
* should be kept alive between client requests.
*/
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
@Override
public Mono<Void> save(MapSession session) {
return Mono.fromRunnable(() -> {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
}
this.sessions.put(session.getId(), new MapSession(session));
});
}
@Override
public Mono<MapSession> findById(String id) {
// @formatter:off
return Mono.defer(() -> Mono.justOrEmpty(this.sessions.get(id))
.filter((session) -> !session.isExpired())
.map(MapSession::new)
.switchIfEmpty(deleteById(id).then(Mono.empty())));
// @formatter:on
}
@Override
public Mono<Void> deleteById(String id) {
return Mono.fromRunnable(() -> this.sessions.remove(id));
}
@Override
public Mono<MapSession> createSession() {
return Mono.defer(() -> {
MapSession result = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
result.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return Mono.just(result);
});
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.
* 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;
import reactor.core.publisher.Mono;
/**
* A repository interface for managing {@link Session} instances.
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @since 2.0
*/
public interface ReactiveSessionRepository<S extends Session> {
/**
* Creates a new {@link Session} that is capable of being persisted by this
* {@link ReactiveSessionRepository}.
*
* <p>
* This allows optimizations and customizations in how the {@link Session} is
* persisted. For example, the implementation returned might keep track of the changes
* ensuring that only the delta needs to be persisted on a save.
* </p>
*
* @return a new {@link Session} that is capable of being persisted by this
* {@link ReactiveSessionRepository}
*/
Mono<S> createSession();
/**
* Ensures the {@link Session} created by
* {@link ReactiveSessionRepository#createSession()} is saved.
*
* <p>
* Some implementations may choose to save as the {@link Session} is updated by
* returning a {@link Session} that immediately persists any changes. In this case,
* this method may not actually do anything.
* </p>
*
* @param session the {@link Session} to save
* @return indicator of operation completion
*/
Mono<Void> save(S session);
/**
* Gets the {@link Session} by the {@link Session#getId()} or null if no
* {@link Session} is found.
*
* @param id the {@link Session#getId()} to lookup
* @return the {@link Session} by the {@link Session#getId()} or null if no
* {@link Session} is found.
*/
Mono<S> findById(String id);
/**
* Deletes the {@link Session} with the given {@link Session#getId()} or does nothing
* if the {@link Session} is not found.
* @param id the {@link Session#getId()} to delete
* @return indicator of operation completion
*/
Mono<Void> deleteById(String id);
}

View File

@@ -0,0 +1,160 @@
/*
* 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;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
/**
* Provides a way to identify a user in an agnostic way. This allows the session to be
* used by an HttpSession, WebSocket Session, or even non web related sessions.
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public interface Session {
/**
* Gets a unique string that identifies the {@link Session}.
*
* @return a unique string that identifies the {@link Session}
*/
String getId();
/**
* Changes the session id. After invoking the {@link #getId()} will return a new identifier.
* @return the new session id which {@link #getId()} will now return
*/
String changeSessionId();
/**
* Gets the Object associated with the specified name or null if no Object is
* associated to that name.
*
* @param <T> the return type of the attribute
* @param attributeName the name of the attribute to get
* @return the Object associated with the specified name or null if no Object is
* associated to that name
*/
<T> T getAttribute(String attributeName);
/**
* Return the session attribute value or if not present raise an
* {@link IllegalArgumentException}.
* @param name the attribute name
* @param <T> the attribute type
* @return the attribute value
*/
@SuppressWarnings("unchecked")
default <T> T getRequiredAttribute(String name) {
T result = getAttribute(name);
if (result == null) {
throw new IllegalArgumentException(
"Required attribute '" + name + "' is missing.");
}
return result;
}
/**
* Return the session attribute value, or a default, fallback value.
* @param name the attribute name
* @param defaultValue a default value to return instead
* @param <T> the attribute type
* @return the attribute value
*/
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
T result = getAttribute(name);
return (result != null) ? result : defaultValue;
}
/**
* Gets the attribute names that have a value associated with it. Each value can be
* passed into {@link org.springframework.session.Session#getAttribute(String)} to
* obtain the attribute value.
*
* @return the attribute names that have a value associated with it.
* @see #getAttribute(String)
*/
Set<String> getAttributeNames();
/**
* Sets the attribute value for the provided attribute name. If the attributeValue is
* null, it has the same result as removing the attribute with
* {@link org.springframework.session.Session#removeAttribute(String)} .
*
* @param attributeName the attribute name to set
* @param attributeValue the value of the attribute to set. If null, the attribute
* will be removed.
*/
void setAttribute(String attributeName, Object attributeValue);
/**
* Removes the attribute with the provided attribute name.
* @param attributeName the name of the attribute to remove
*/
void removeAttribute(String attributeName);
/**
* Gets the time when this session was created.
*
* @return the time when this session was created.
*/
Instant getCreationTime();
/**
* Sets the last accessed time.
*
* @param lastAccessedTime the last accessed time
*/
void setLastAccessedTime(Instant lastAccessedTime);
/**
* Gets the last time this {@link Session} was accessed.
*
* @return the last time the client sent a request associated with the session
*/
Instant getLastAccessedTime();
/**
* Sets the maximum inactive interval between requests before this session will be
* invalidated. A negative time indicates that the session will never timeout.
*
* @param interval the amount of time that the {@link Session} should be kept alive
* between client requests.
*/
void setMaxInactiveInterval(Duration interval);
/**
* Gets the maximum inactive interval between requests before this session will be
* invalidated. A negative time indicates that the session will never timeout.
*
* @return the maximum inactive interval between requests before this session will be
* invalidated. A negative time indicates that the session will never timeout.
*/
Duration getMaxInactiveInterval();
/**
* Returns true if the session is expired.
*
* @return true if the session is expired, else false.
*/
boolean isExpired();
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session;
/**
* A repository interface for managing {@link Session} instances.
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @since 1.0
*/
public interface SessionRepository<S extends Session> {
/**
* Creates a new {@link Session} that is capable of being persisted by this
* {@link SessionRepository}.
*
* <p>
* This allows optimizations and customizations in how the {@link Session} is
* persisted. For example, the implementation returned might keep track of the changes
* ensuring that only the delta needs to be persisted on a save.
* </p>
*
* @return a new {@link Session} that is capable of being persisted by this
* {@link SessionRepository}
*/
S createSession();
/**
* Ensures the {@link Session} created by
* {@link org.springframework.session.SessionRepository#createSession()} is saved.
*
* <p>
* Some implementations may choose to save as the {@link Session} is updated by
* returning a {@link Session} that immediately persists any changes. In this case,
* this method may not actually do anything.
* </p>
*
* @param session the {@link Session} to save
*/
void save(S session);
/**
* Gets the {@link Session} by the {@link Session#getId()} or null if no
* {@link Session} is found.
*
* @param id the {@link org.springframework.session.Session#getId()} to lookup
* @return the {@link Session} by the {@link Session#getId()} or null if no
* {@link Session} is found.
*/
S findById(String id);
/**
* Deletes the {@link Session} with the given {@link Session#getId()} or does nothing
* if the {@link Session} is not found.
* @param id the {@link org.springframework.session.Session#getId()} to delete
*/
void deleteById(String id);
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.config.annotation.web.http;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.session.SessionRepository;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
/**
* Add this annotation to an {@code @Configuration} class to expose the
* SessionRepositoryFilter as a bean named "springSessionRepositoryFilter" and backed by a
* user provided implementation of {@link SessionRepository}. In order to leverage the
* annotation, a single {@link SessionRepository} bean must be provided. For example:
*
* <pre>
* <code>
* {@literal @Configuration}
* {@literal @EnableSpringHttpSession}
* public class SpringHttpSessionConfig {
*
* {@literal @Bean}
* public MapSessionRepository sessionRepository() {
* return new MapSessionRepository();
* }
*
* }
* </code> </pre>
*
* <p>
* It is important to note that no infrastructure for session expirations is configured
* for you out of the box. This is because things like session expiration are highly
* implementation dependent. This means if you require cleaning up expired sessions, you
* are responsible for cleaning up the expired sessions.
* </p>
*
* <p>
* The following is provided for you with the base configuration:
* </p>
*
* <ul>
* <li>SessionRepositoryFilter - is responsible for wrapping the HttpServletRequest with
* an implementation of HttpSession that is backed by a SessionRepository</li>
* <li>SessionEventHttpSessionListenerAdapter - is responsible for translating Spring
* Session events into HttpSessionEvent. In order for it to work, the implementation of
* SessionRepository you provide must support {@link SessionCreatedEvent} and
* {@link SessionDestroyedEvent}.</li>
* <li>
* </ul>
*
* @author Rob Winch
* @since 1.1
*/
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ java.lang.annotation.ElementType.TYPE })
@Documented
@Import(SpringHttpSessionConfiguration.class)
@Configuration
public @interface EnableSpringHttpSession {
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2014-2019 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.config.annotation.web.http;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import javax.servlet.SessionCookieConfig;
import javax.servlet.http.HttpSessionListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Configures the basics for setting up Spring Session in a web environment. In order to
* use it, you must provide a {@link SessionRepository}. For example:
*
* <pre>
* {@literal @Configuration}
* {@literal @EnableSpringHttpSession}
* public class SpringHttpSessionConfig {
*
* {@literal @Bean}
* public MapSessionRepository sessionRepository() {
* return new MapSessionRepository();
* }
*
* }
* </pre>
*
* <p>
* It is important to note that no infrastructure for session expirations is configured
* for you out of the box. This is because things like session expiration are highly
* implementation dependent. This means if you require cleaning up expired sessions, you
* are responsible for cleaning up the expired sessions.
* </p>
*
* <p>
* The following is provided for you with the base configuration:
* </p>
*
* <ul>
* <li>SessionRepositoryFilter - is responsible for wrapping the HttpServletRequest with
* an implementation of HttpSession that is backed by a SessionRepository</li>
* <li>SessionEventHttpSessionListenerAdapter - is responsible for translating Spring
* Session events into HttpSessionEvent. In order for it to work, the implementation of
* SessionRepository you provide must support {@link SessionCreatedEvent} and
* {@link SessionDestroyedEvent}.</li>
* <li>
* </ul>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.1
*
* @see EnableSpringHttpSession
*/
@Configuration(proxyBeanMethods = false)
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver();
private boolean usesSpringSessionRememberMeServices;
private ServletContext servletContext;
private CookieSerializer cookieSerializer;
private HttpSessionIdResolver httpSessionIdResolver = this.defaultHttpSessionIdResolver;
private List<HttpSessionListener> httpSessionListeners = new ArrayList<>();
@PostConstruct
public void init() {
CookieSerializer cookieSerializer = (this.cookieSerializer != null)
? this.cookieSerializer
: createDefaultCookieSerializer();
this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
}
@Bean
public SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter() {
return new SessionEventHttpSessionListenerAdapter(this.httpSessionListeners);
}
@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
sessionRepository);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (ClassUtils.isPresent(
"org.springframework.security.web.authentication.RememberMeServices",
null)) {
this.usesSpringSessionRememberMeServices = !ObjectUtils
.isEmpty(applicationContext
.getBeanNamesForType(SpringSessionRememberMeServices.class));
}
}
@Autowired(required = false)
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Autowired(required = false)
public void setCookieSerializer(CookieSerializer cookieSerializer) {
this.cookieSerializer = cookieSerializer;
}
@Autowired(required = false)
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
this.httpSessionIdResolver = httpSessionIdResolver;
}
@Autowired(required = false)
public void setHttpSessionListeners(List<HttpSessionListener> listeners) {
this.httpSessionListeners = listeners;
}
private CookieSerializer createDefaultCookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
if (this.servletContext != null) {
SessionCookieConfig sessionCookieConfig = null;
try {
sessionCookieConfig = this.servletContext.getSessionCookieConfig();
}
catch (UnsupportedOperationException ex) {
this.logger
.warn("Unable to obtain SessionCookieConfig: " + ex.getMessage());
}
if (sessionCookieConfig != null) {
if (sessionCookieConfig.getName() != null) {
cookieSerializer.setCookieName(sessionCookieConfig.getName());
}
if (sessionCookieConfig.getDomain() != null) {
cookieSerializer.setDomainName(sessionCookieConfig.getDomain());
}
if (sessionCookieConfig.getPath() != null) {
cookieSerializer.setCookiePath(sessionCookieConfig.getPath());
}
if (sessionCookieConfig.getMaxAge() != -1) {
cookieSerializer.setCookieMaxAge(sessionCookieConfig.getMaxAge());
}
}
}
if (this.usesSpringSessionRememberMeServices) {
cookieSerializer.setRememberMeRequestAttribute(
SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR);
}
return cookieSerializer;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.config.annotation.web.server;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Add this annotation to a {@code @Configuration} class to configure a {@code WebSessionManager} for a WebFlux
* application. This annotation assumes a {@code ReactiveSessionRepository} is defined somewhere in the application
* context. If not, it will fail with a clear error message. For example:
*
* <pre>
* <code>
* {@literal @Configuration}
* {@literal @EnableSpringWebSession}
* public class SpringWebFluxConfig {
*
* {@literal @Bean}
* public ReactiveSessionRepository sessionRepository() {
* return new ReactiveMapSessionRepository();
* }
*
* }
* </code>
* </pre>
*
* @author Greg Turnquist
* @since 2.0
*/
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ java.lang.annotation.ElementType.TYPE })
@Documented
@Import(SpringWebSessionConfiguration.class)
@Configuration
public @interface EnableSpringWebSession {
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2014-2019 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.config.annotation.web.server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.web.server.session.SpringSessionWebSessionStore;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionIdResolver;
import org.springframework.web.server.session.WebSessionManager;
/**
* Wire up a {@link WebSessionManager} using a Reactive {@link ReactiveSessionRepository} from the application context.
*
* @author Greg Turnquist
* @author Rob Winch
* @since 2.0
*
* @see EnableSpringWebSession
*/
@Configuration(proxyBeanMethods = false)
public class SpringWebSessionConfiguration {
/**
* Optional override of default {@link WebSessionIdResolver}.
*/
@Autowired(required = false)
private WebSessionIdResolver webSessionIdResolver;
/**
* Configure a {@link WebSessionManager} using a provided {@link ReactiveSessionRepository}.
*
* @param repository a bean that implements {@link ReactiveSessionRepository}.
* @return a configured {@link WebSessionManager} registered with a preconfigured name.
*/
@Bean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
public WebSessionManager webSessionManager(ReactiveSessionRepository<? extends Session> repository) {
SpringSessionWebSessionStore<? extends Session> sessionStore = new SpringSessionWebSessionStore<>(repository);
DefaultWebSessionManager manager = new DefaultWebSessionManager();
manager.setSessionStore(sessionStore);
if (this.webSessionIdResolver != null) {
manager.setSessionIdResolver(this.webSessionIdResolver);
}
return manager;
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.events;
import org.springframework.context.ApplicationEvent;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
/**
* For {@link SessionRepository} implementations that support it, this event is fired when
* a {@link Session} is updated.
*
* @author Rob Winch
* @since 1.1
*/
@SuppressWarnings("serial")
public abstract class AbstractSessionEvent extends ApplicationEvent {
private final String sessionId;
private final Session session;
AbstractSessionEvent(Object source, Session session) {
super(source);
this.session = session;
this.sessionId = session.getId();
}
/**
* Gets the {@link Session} that was destroyed. For some {@link SessionRepository}
* implementations it may not be possible to get the original session in which case
* this may be null.
*
* @param <S> the type of Session
* @return the expired {@link Session} or null if the data store does not support
* obtaining it
*/
@SuppressWarnings("unchecked")
public <S extends Session> S getSession() {
return (S) this.session;
}
public String getSessionId() {
return this.sessionId;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.
* 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.events;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
/**
* For {@link SessionRepository} implementations that support it, this event is fired when
* a {@link Session} is created.
*
* @author Rob Winch
* @since 1.0
*/
@SuppressWarnings("serial")
public class SessionCreatedEvent extends AbstractSessionEvent {
/**
* Create a new {@link SessionCreatedEvent}.
* @param source the source of the event
* @param session the session that was created
*/
public SessionCreatedEvent(Object source, Session session) {
super(source, session);
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.
* 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.events;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
/**
* For {@link SessionRepository} implementations that support it, this event is fired when
* a {@link Session} is destroyed via deletion.
*
* @author Mark Anderson
* @author Rob Winch
* @since 1.1
*/
@SuppressWarnings("serial")
public class SessionDeletedEvent extends SessionDestroyedEvent {
/**
* Create a new {@link SessionDeletedEvent}.
* @param source the source of the event
* @param session the session that was created
*/
public SessionDeletedEvent(Object source, Session session) {
super(source, session);
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.
* 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.events;
import org.springframework.session.Session;
/**
* Base class for events fired when a {@link Session} is destroyed explicitly.
*
* @author Rob Winch
* @since 1.0
*/
@SuppressWarnings("serial")
public class SessionDestroyedEvent extends AbstractSessionEvent {
/**
* Create a new {@link SessionDestroyedEvent}.
* @param source the source of the event
* @param session the session that was created
*/
public SessionDestroyedEvent(Object source, Session session) {
super(source, session);
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.
* 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.events;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
/**
* For {@link SessionRepository} implementations that support it, this event is fired when
* a {@link Session} is destroyed via expiration.
*
* @author Mark Anderson
* @author Rob Winch
* @since 1.1
*/
@SuppressWarnings("serial")
public class SessionExpiredEvent extends SessionDestroyedEvent {
/**
* Create a new {@link SessionExpiredEvent}.
* @param source the source of the event
* @param session the session that was created
*/
public SessionExpiredEvent(Object source, Session session) {
super(source, session);
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.security;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
/**
* Ensures that calling {@link #expireNow()} propagates to Spring Session, since this
* session information contains only derived data and is not the authoritative source.
*
* @param <S> the {@link Session} type.
* @author Joris Kuipers
* @author Vedran Pavic
* @since 1.3
*/
class SpringSessionBackedSessionInformation<S extends Session>
extends SessionInformation {
static final String EXPIRED_ATTR = SpringSessionBackedSessionInformation.class
.getName() + ".EXPIRED";
private static final Log logger = LogFactory
.getLog(SpringSessionBackedSessionInformation.class);
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private final SessionRepository<S> sessionRepository;
SpringSessionBackedSessionInformation(S session,
SessionRepository<S> sessionRepository) {
super(resolvePrincipal(session), session.getId(),
Date.from(session.getLastAccessedTime()));
this.sessionRepository = sessionRepository;
Boolean expired = session.getAttribute(EXPIRED_ATTR);
if (Boolean.TRUE.equals(expired)) {
super.expireNow();
}
}
/**
* Tries to determine the principal's name from the given Session.
*
* @param session the session
* @return the principal's name, or empty String if it couldn't be determined
*/
private static String resolvePrincipal(Session session) {
String principalName = session
.getAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
if (principalName != null) {
return principalName;
}
SecurityContext securityContext = session
.getAttribute(SPRING_SECURITY_CONTEXT);
if (securityContext != null
&& securityContext.getAuthentication() != null) {
return securityContext.getAuthentication().getName();
}
return "";
}
@Override
public void expireNow() {
if (logger.isDebugEnabled()) {
logger.debug("Expiring session " + getSessionId() + " for user '"
+ getPrincipal() + "', presumably because maximum allowed concurrent "
+ "sessions was exceeded");
}
super.expireNow();
S session = this.sessionRepository.findById(getSessionId());
if (session != null) {
session.setAttribute(EXPIRED_ATTR, Boolean.TRUE);
this.sessionRepository.save(session);
}
else {
logger.info("Could not find Session with id " + getSessionId()
+ " to mark as expired");
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.security;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
/**
* A {@link SessionRegistry} that retrieves session information from Spring Session,
* rather than maintaining it itself. This allows concurrent session management with
* Spring Security in a clustered environment.
* <p>
* Relies on being able to derive the same String-based representation of the principal
* given to {@link #getAllSessions(Object, boolean)} as used by Spring Session in order to
* look up the user's sessions.
* <p>
* Does not support {@link #getAllPrincipals()}, since that information is not available.
*
* @param <S> the {@link Session} type.
* @author Joris Kuipers
* @author Vedran Pavic
* @since 1.3
*/
public class SpringSessionBackedSessionRegistry<S extends Session>
implements SessionRegistry {
private final FindByIndexNameSessionRepository<S> sessionRepository;
public SpringSessionBackedSessionRegistry(
FindByIndexNameSessionRepository<S> sessionRepository) {
Assert.notNull(sessionRepository, "sessionRepository cannot be null");
this.sessionRepository = sessionRepository;
}
@Override
public List<Object> getAllPrincipals() {
throw new UnsupportedOperationException("SpringSessionBackedSessionRegistry does "
+ "not support retrieving all principals, since Spring Session provides "
+ "no way to obtain that information");
}
@Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository
.findByPrincipalName(name(principal)).values();
List<SessionInformation> infos = new ArrayList<>();
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session
.getAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR))) {
infos.add(new SpringSessionBackedSessionInformation<>(session,
this.sessionRepository));
}
}
return infos;
}
@Override
public SessionInformation getSessionInformation(String sessionId) {
S session = this.sessionRepository.findById(sessionId);
if (session != null) {
return new SpringSessionBackedSessionInformation<>(session,
this.sessionRepository);
}
return null;
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
@Override
public void refreshLastRequest(String sessionId) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
@Override
public void registerNewSession(String sessionId, Object principal) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
@Override
public void removeSessionInformation(String sessionId) {
}
/**
* Derives a String name for the given principal.
*
* @param principal as provided by Spring Security
* @return name of the principal, or its {@code toString()} representation if no name
* could be derived
*/
protected String name(Object principal) {
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
}
if (principal instanceof Principal) {
return ((Principal) principal).getName();
}
return principal.toString();
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.security.web.authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.util.Assert;
/**
* A {@link RememberMeServices} implementation that uses Spring Session backed
* {@link HttpSession} to provide remember-me service capabilities.
*
* @author Vedran Pavic
* @since 1.3.0
*/
public class SpringSessionRememberMeServices
implements RememberMeServices, LogoutHandler {
/**
* Remember-me login request attribute name.
*/
public static final String REMEMBER_ME_LOGIN_ATTR = SpringSessionRememberMeServices.class
.getName() + "REMEMBER_ME_LOGIN_ATTR";
private static final String DEFAULT_REMEMBERME_PARAMETER = "remember-me";
private static final int THIRTY_DAYS_SECONDS = 2592000;
private static final Log logger = LogFactory
.getLog(SpringSessionRememberMeServices.class);
private String rememberMeParameterName = DEFAULT_REMEMBERME_PARAMETER;
private boolean alwaysRemember;
private int validitySeconds = THIRTY_DAYS_SECONDS;
private String sessionAttrToDeleteOnLoginFail = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
@Override
public final Authentication autoLogin(HttpServletRequest request,
HttpServletResponse response) {
return null;
}
@Override
public final void loginFail(HttpServletRequest request,
HttpServletResponse response) {
logout(request);
}
@Override
public final void loginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {
if (!this.alwaysRemember
&& !rememberMeRequested(request, this.rememberMeParameterName)) {
logger.debug("Remember-me login not requested.");
return;
}
request.setAttribute(REMEMBER_ME_LOGIN_ATTR, true);
request.getSession().setMaxInactiveInterval(this.validitySeconds);
}
/**
* Allows customization of whether a remember-me login has been requested. The default
* is to return {@code true} if the configured parameter name has been included in the
* request and is set to the value {@code true}.
* @param request the request submitted from an interactive login, which may include
* additional information indicating that a persistent login is desired.
* @param parameter the configured remember-me parameter name.
* @return true if the request includes information indicating that a persistent login
* has been requested.
*/
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
String rememberMe = request.getParameter(parameter);
if (rememberMe != null) {
if (rememberMe.equalsIgnoreCase("true") || rememberMe.equalsIgnoreCase("on")
|| rememberMe.equalsIgnoreCase("yes") || rememberMe.equals("1")) {
return true;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Did not send remember-me cookie (principal did not set "
+ "parameter '" + parameter + "')");
}
return false;
}
/**
* Set the name of the parameter which should be checked for to see if a remember-me
* has been requested during a login request. This should be the same name you assign
* to the checkbox in your login form.
* @param rememberMeParameterName the request parameter
*/
public void setRememberMeParameterName(String rememberMeParameterName) {
Assert.hasText(rememberMeParameterName,
"rememberMeParameterName cannot be empty or null");
this.rememberMeParameterName = rememberMeParameterName;
}
public void setAlwaysRemember(boolean alwaysRemember) {
this.alwaysRemember = alwaysRemember;
}
public void setValiditySeconds(int validitySeconds) {
this.validitySeconds = validitySeconds;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
logout(request);
}
private void logout(HttpServletRequest request) {
logger.debug("Interactive login attempt was unsuccessful.");
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(this.sessionAttrToDeleteOnLoginFail);
}
}
}

View File

@@ -0,0 +1,287 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.context;
import java.util.Arrays;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Conventions;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.Assert;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
* Registers the {@link DelegatingFilterProxy} to use the springSessionRepositoryFilter
* before any other registered {@link Filter}. When used with
* {@link #AbstractHttpSessionApplicationInitializer(Class...)}, it will also register a
* {@link ContextLoaderListener}. When used with
* {@link #AbstractHttpSessionApplicationInitializer()}, this class is typically used in
* addition to a subclass of {@link AbstractContextLoaderInitializer}.
*
* <p>
* By default the {@link DelegatingFilterProxy} is registered with support for
* asynchronous requests, but can be enabled by overriding
* {@link #isAsyncSessionSupported()} and {@link #getSessionDispatcherTypes()}.
* </p>
*
* <p>
* Additional configuration before and after the springSecurityFilterChain can be added by
* overriding {@link #afterSessionRepositoryFilter(ServletContext)}.
* </p>
*
*
* <h2>Caveats</h2>
* <p>
* Subclasses of {@code AbstractDispatcherServletInitializer} will register their filters
* before any other {@link Filter}. This means that you will typically want to ensure
* subclasses of {@code AbstractDispatcherServletInitializer} are invoked first. This can
* be done by ensuring the {@link Order} or {@link Ordered} of
* {@code AbstractDispatcherServletInitializer} are sooner than subclasses of
* {@code AbstractSecurityWebApplicationInitializer}.
* </p>
*
* @author Rob Winch
*
*/
@Order(100)
public abstract class AbstractHttpSessionApplicationInitializer
implements WebApplicationInitializer {
private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";
/**
* The default name for Spring Session's repository filter.
*/
public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter";
private final Class<?>[] configurationClasses;
/**
* Creates a new instance that assumes the Spring Session configuration is loaded by
* some other means than this class. For example, a user might create a
* {@link ContextLoaderListener} using a subclass of
* {@link AbstractContextLoaderInitializer}.
*
* @see ContextLoaderListener
*/
protected AbstractHttpSessionApplicationInitializer() {
this.configurationClasses = null;
}
/**
* Creates a new instance that will instantiate the {@link ContextLoaderListener} with
* the specified classes.
*
* @param configurationClasses {@code @Configuration} classes that will be used to
* configure the context
*/
protected AbstractHttpSessionApplicationInitializer(
Class<?>... configurationClasses) {
this.configurationClasses = configurationClasses;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
beforeSessionRepositoryFilter(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
insertSessionRepositoryFilter(servletContext);
afterSessionRepositoryFilter(servletContext);
}
/**
* Registers the springSessionRepositoryFilter.
* @param servletContext the {@link ServletContext}
*/
private void insertSessionRepositoryFilter(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME;
DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(
filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSessionRepositoryFilter.setContextAttribute(contextAttribute);
}
registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);
}
/**
* Inserts the provided {@link Filter}s before existing {@link Filter}s using default
* generated names, {@link #getSessionDispatcherTypes()}, and
* {@link #isAsyncSessionSupported()}.
*
* @param servletContext the {@link ServletContext} to use
* @param filters the {@link Filter}s to register
*/
protected final void insertFilters(ServletContext servletContext, Filter... filters) {
registerFilters(servletContext, true, filters);
}
/**
* Inserts the provided {@link Filter}s after existing {@link Filter}s using default
* generated names, {@link #getSessionDispatcherTypes()}, and
* {@link #isAsyncSessionSupported()}.
*
* @param servletContext the {@link ServletContext} to use
* @param filters the {@link Filter}s to register
*/
protected final void appendFilters(ServletContext servletContext, Filter... filters) {
registerFilters(servletContext, false, filters);
}
/**
* Registers the provided {@link Filter}s using default generated names,
* {@link #getSessionDispatcherTypes()}, and {@link #isAsyncSessionSupported()}.
*
* @param servletContext the {@link ServletContext} to use
* @param insertBeforeOtherFilters if true, will insert the provided {@link Filter}s
* before other {@link Filter}s. Otherwise, will insert the {@link Filter}s after
* other {@link Filter}s.
* @param filters the {@link Filter}s to register
*/
private void registerFilters(ServletContext servletContext,
boolean insertBeforeOtherFilters, Filter... filters) {
Assert.notEmpty(filters, "filters cannot be null or empty");
for (Filter filter : filters) {
if (filter == null) {
throw new IllegalArgumentException(
"filters cannot contain null values. Got "
+ Arrays.asList(filters));
}
String filterName = Conventions.getVariableName(filter);
registerFilter(servletContext, insertBeforeOtherFilters, filterName, filter);
}
}
/**
* Registers the provided filter using the {@link #isAsyncSessionSupported()} and
* {@link #getSessionDispatcherTypes()}.
*
* @param servletContext the servlet context
* @param insertBeforeOtherFilters should this Filter be inserted before or after
* other {@link Filter}
* @param filterName the filter name
* @param filter the filter
*/
private void registerFilter(ServletContext servletContext,
boolean insertBeforeOtherFilters, String filterName, Filter filter) {
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
throw new IllegalStateException(
"Duplicate Filter registration for '" + filterName
+ "'. Check to ensure the Filter is only configured once.");
}
registration.setAsyncSupported(isAsyncSessionSupported());
EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
"/*");
}
/**
* Returns the {@link DelegatingFilterProxy#getContextAttribute()} or null if the
* parent {@link ApplicationContext} should be used. The default behavior is to use
* the parent {@link ApplicationContext}.
*
* <p>
* If {@link #getDispatcherWebApplicationContextSuffix()} is non-null the
* {@link WebApplicationContext} for the Dispatcher will be used. This means the child
* {@link ApplicationContext} is used to look up the springSessionRepositoryFilter
* bean.
* </p>
*
* @return the {@link DelegatingFilterProxy#getContextAttribute()} or null if the
* parent {@link ApplicationContext} should be used
*/
private String getWebApplicationContextAttribute() {
String dispatcherServletName = getDispatcherWebApplicationContextSuffix();
if (dispatcherServletName == null) {
return null;
}
return SERVLET_CONTEXT_PREFIX + dispatcherServletName;
}
/**
* Return the {@code <servlet-name>} to use the DispatcherServlet's
* {@link WebApplicationContext} to find the {@link DelegatingFilterProxy} or null to
* use the parent {@link ApplicationContext}.
*
* <p>
* For example, if you are using AbstractDispatcherServletInitializer or
* AbstractAnnotationConfigDispatcherServletInitializer and using the provided Servlet
* name, you can return "dispatcher" from this method to use the DispatcherServlet's
* {@link WebApplicationContext}.
* </p>
*
* @return the {@code <servlet-name>} of the DispatcherServlet to use its
* {@link WebApplicationContext} or null (default) to use the parent
* {@link ApplicationContext}.
*/
protected String getDispatcherWebApplicationContextSuffix() {
return null;
}
/**
* Invoked before the springSessionRepositoryFilter is added.
* @param servletContext the {@link ServletContext}
*/
protected void beforeSessionRepositoryFilter(ServletContext servletContext) {
}
/**
* Invoked after the springSessionRepositoryFilter is added.
* @param servletContext the {@link ServletContext}
*/
protected void afterSessionRepositoryFilter(ServletContext servletContext) {
}
/**
* Get the {@link DispatcherType} for the springSessionRepositoryFilter.
* @return the {@link DispatcherType} for the filter
*/
protected EnumSet<DispatcherType> getSessionDispatcherTypes() {
return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR,
DispatcherType.ASYNC);
}
/**
* Determine if the springSessionRepositoryFilter should be marked as supporting
* asynch. Default is true.
*
* @return true if springSessionRepositoryFilter should be marked as supporting asynch
*/
protected boolean isAsyncSessionSupported() {
return true;
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.
* 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.web.http;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
/**
* A {@link HttpSessionIdResolver} that uses a cookie to obtain the session from.
* Specifically, this implementation will allow specifying a cookie serialization strategy
* using {@link CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer)}. The
* default is cookie name is "SESSION".
*
* When a session is created, the HTTP response will have a cookie with the specified
* cookie name and the value of the session id. The cookie will be marked as a session
* cookie, use the context path for the path of the cookie, marked as HTTPOnly, and if
* {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will
* be marked as secure. For example:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
* </pre>
*
* The client should now include the session in each request by specifying the same cookie
* in their request. For example:
*
* <pre>
* GET /messages/ HTTP/1.1
* Host: example.com
* Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* When the session is invalidated, the server will send an HTTP response that expires the
* cookie. For example:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
* </pre>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public final class CookieHttpSessionIdResolver implements HttpSessionIdResolver {
private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class
.getName().concat(".WRITTEN_SESSION_ID_ATTR");
private CookieSerializer cookieSerializer = new DefaultCookieSerializer();
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
return this.cookieSerializer.readCookieValues(request);
}
@Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId) {
if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
return;
}
request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, sessionId));
}
@Override
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));
}
/**
* Sets the {@link CookieSerializer} to be used.
*
* @param cookieSerializer the cookieSerializer to set. Cannot be null.
*/
public void setCookieSerializer(CookieSerializer cookieSerializer) {
if (cookieSerializer == null) {
throw new IllegalArgumentException("cookieSerializer cannot be null");
}
this.cookieSerializer = cookieSerializer;
}
}

View File

@@ -0,0 +1,137 @@
/*
* 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.web.http;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Strategy for reading and writing a cookie value to the {@link HttpServletResponse}.
*
* @author Rob Winch
* @since 1.1
*/
public interface CookieSerializer {
/**
* Writes a given {@link CookieValue} to the provided {@link HttpServletResponse}.
*
* @param cookieValue the {@link CookieValue} to write to
* {@link CookieValue#getResponse()}. Cannot be null.
*/
void writeCookieValue(CookieValue cookieValue);
/**
* Reads all the matching cookies from the {@link HttpServletRequest}. The result is a
* List since there can be multiple {@link Cookie} in a single request with a matching
* name. For example, one Cookie may have a path of / and another of /context, but the
* path is not transmitted in the request.
*
* @param request the {@link HttpServletRequest} to read the cookie from. Cannot be
* null.
* @return the values of all the matching cookies
*/
List<String> readCookieValues(HttpServletRequest request);
/**
* Contains the information necessary to write a value to the
* {@link HttpServletResponse}.
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.1
*/
class CookieValue {
private final HttpServletRequest request;
private final HttpServletResponse response;
private final String cookieValue;
private int cookieMaxAge = -1;
/**
* Creates a new instance.
*
* @param request the {@link HttpServletRequest} to use. Useful for determining
* the context in which the cookie is set. Cannot be null.
* @param response the {@link HttpServletResponse} to use.
* @param cookieValue the value of the cookie to be written. This value may be
* modified by the {@link CookieSerializer} when writing to the actual cookie so
* long as the original value is returned when the cookie is read.
*/
public CookieValue(HttpServletRequest request, HttpServletResponse response,
String cookieValue) {
this.request = request;
this.response = response;
this.cookieValue = cookieValue;
if ("".equals(this.cookieValue)) {
this.cookieMaxAge = 0;
}
}
/**
* Gets the request to use.
* @return the request to use. Cannot be null.
*/
public HttpServletRequest getRequest() {
return this.request;
}
/**
* Gets the response to write to.
* @return the response to write to. Cannot be null.
*/
public HttpServletResponse getResponse() {
return this.response;
}
/**
* The value to be written. This value may be modified by the
* {@link CookieSerializer} before written to the cookie. However, the value must
* be the same as the original when it is read back in.
*
* @return the value to be written
*/
public String getCookieValue() {
return this.cookieValue;
}
/**
* Get the cookie max age. The default is -1 which signals to delete the cookie
* when the browser is closed, or 0 if cookie value is empty.
* @return the cookie max age
*/
public int getCookieMaxAge() {
return this.cookieMaxAge;
}
/**
* Set the cookie max age.
* @param cookieMaxAge the cookie max age
*/
public void setCookieMaxAge(int cookieMaxAge) {
this.cookieMaxAge = cookieMaxAge;
}
}
}

View File

@@ -0,0 +1,455 @@
/*
* 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.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;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* The default implementation of {@link CookieSerializer}.
*
* @author Rob Winch
* @author Vedran Pavic
* @author Eddú Meléndez
* @since 1.1
*/
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;
private boolean useHttpOnlyCookie = true;
private String cookiePath;
private Integer cookieMaxAge;
private String domainName;
private Pattern domainNamePattern;
private String jvmRoute;
private boolean useBase64Encoding = true;
private String rememberMeRequestAttribute;
private String sameSite = "Lax";
/*
* (non-Javadoc)
*
* @see org.springframework.session.web.http.CookieSerializer#readCookieValues(javax.
* servlet.http.HttpServletRequest)
*/
@Override
public List<String> readCookieValues(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
List<String> matchingCookieValues = new ArrayList<>();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (this.cookieName.equals(cookie.getName())) {
String sessionId = (this.useBase64Encoding
? base64Decode(cookie.getValue())
: cookie.getValue());
if (sessionId == null) {
continue;
}
if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
sessionId = sessionId.substring(0,
sessionId.length() - this.jvmRoute.length());
}
matchingCookieValues.add(sessionId);
}
}
}
return matchingCookieValues;
}
/*
* (non-Javadoc)
*
* @see org.springframework.session.web.http.CookieWriter#writeCookieValue(org.
* springframework.session.web.http.CookieWriter.CookieValue)
*/
@Override
public void writeCookieValue(CookieValue cookieValue) {
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);
}
response.addHeader("Set-Cookie", sb.toString());
}
/**
* Decode the value using Base64.
* @param base64Value the Base64 String to decode
* @return the Base64 decoded value
* @since 1.2.2
*/
private String base64Decode(String base64Value) {
try {
byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
return new String(decodedCookieBytes);
}
catch (Exception ex) {
logger.debug("Unable to Base64 decode value: " + base64Value);
return null;
}
}
/**
* Encode the value using Base64.
* @param value the String to Base64 encode
* @return the Base64 encoded value
* @since 1.2.2
*/
private String base64Encode(String value) {
byte[] encodedCookieBytes = Base64.getEncoder().encode(value.getBytes());
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()}.
*
* @param useSecureCookie determines if the cookie should be marked as secure.
*/
public void setUseSecureCookie(boolean useSecureCookie) {
this.useSecureCookie = useSecureCookie;
}
/**
* Sets if a Cookie marked as HTTP Only should be used. The default is true.
*
* @param useHttpOnlyCookie determines if the cookie should be marked as HTTP Only.
*/
public void setUseHttpOnlyCookie(boolean useHttpOnlyCookie) {
this.useHttpOnlyCookie = useHttpOnlyCookie;
}
private boolean isSecureCookie(HttpServletRequest request) {
if (this.useSecureCookie == null) {
return request.isSecure();
}
return this.useSecureCookie;
}
/**
* Sets the path of the Cookie. The default is to use the context path from the
* {@link HttpServletRequest}.
*
* @param cookiePath the path of the Cookie. If null, the default of the context path
* will be used.
*/
public void setCookiePath(String cookiePath) {
this.cookiePath = cookiePath;
}
public void setCookieName(String cookieName) {
if (cookieName == null) {
throw new IllegalArgumentException("cookieName cannot be null");
}
this.cookieName = cookieName;
}
/**
* Sets the maxAge property of the Cookie. The default is to delete the cookie when
* the browser is closed.
*
* @param cookieMaxAge the maxAge property of the Cookie
*/
public void setCookieMaxAge(int cookieMaxAge) {
this.cookieMaxAge = cookieMaxAge;
}
/**
* Sets an explicit Domain Name. This allow the domain of "example.com" to be used
* when the request comes from www.example.com. This allows for sharing the cookie
* across subdomains. The default is to use the current domain.
*
* @param domainName the name of the domain to use. (i.e. "example.com")
* @throws IllegalStateException if the domainNamePattern is also set
*/
public void setDomainName(String domainName) {
if (this.domainNamePattern != null) {
throw new IllegalStateException(
"Cannot set both domainName and domainNamePattern");
}
this.domainName = domainName;
}
/**
* <p>
* Sets a case insensitive pattern used to extract the domain name from the
* {@link HttpServletRequest#getServerName()}. The pattern should provide a single
* grouping that defines what the value is that should be matched. User's should be
* careful not to output malicious characters like new lines to prevent from things
* like <a href= "https://www.owasp.org/index.php/HTTP_Response_Splitting">HTTP
* Response Splitting</a>.
* </p>
*
* <p>
* If the pattern does not match, then no domain will be set. This is useful to ensure
* the domain is not set during development when localhost might be used.
* </p>
* <p>
* An example value might be "^.+?\\.(\\w+\\.[a-z]+)$". For the given input, it would
* provide the following explicit domain (null means no domain name is set):
* </p>
*
* <ul>
* <li>example.com - null</li>
* <li>child.sub.example.com - example.com</li>
* <li>localhost - null</li>
* <li>127.0.1.1 - null</li>
* </ul>
*
* @param domainNamePattern the case insensitive pattern to extract the domain name
* with
* @throws IllegalStateException if the domainName is also set
*/
public void setDomainNamePattern(String domainNamePattern) {
if (this.domainName != null) {
throw new IllegalStateException(
"Cannot set both domainName and domainNamePattern");
}
this.domainNamePattern = Pattern.compile(domainNamePattern,
Pattern.CASE_INSENSITIVE);
}
/**
* <p>
* Used to identify which JVM to route to for session affinity. With some
* implementations (i.e. Redis) this provides no performance benefit. However, this
* can help with tracing logs of a particular user. This will ensure that the value of
* the cookie is formatted as
* </p>
* <code>
* sessionId + "." jvmRoute
* </code>
* <p>
* To use set a custom route on each JVM instance and setup a frontend proxy to
* forward all requests to the JVM based on the route.
* </p>
*
* @param jvmRoute the JVM Route to use (i.e. "node01jvmA", "n01ja", etc)
*/
public void setJvmRoute(String jvmRoute) {
this.jvmRoute = "." + jvmRoute;
}
/**
* Set if the Base64 encoding of cookie value should be used. This is valuable in
* order to support <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> which
* recommends using Base 64 encoding to the cookie value.
*
* @param useBase64Encoding the flag to indicate whether to use Base64 encoding
*/
public void setUseBase64Encoding(boolean useBase64Encoding) {
this.useBase64Encoding = useBase64Encoding;
}
/**
* Set the request attribute name that indicates remember-me login. If specified, the
* cookie will be written as Integer.MAX_VALUE.
* @param rememberMeRequestAttribute the remember-me request attribute name
* @since 1.3.0
*/
public void setRememberMeRequestAttribute(String rememberMeRequestAttribute) {
if (rememberMeRequestAttribute == null) {
throw new IllegalArgumentException(
"rememberMeRequestAttribute cannot be null");
}
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;
}
if (this.domainNamePattern != null) {
Matcher matcher = this.domainNamePattern.matcher(request.getServerName());
if (matcher.matches()) {
return matcher.group(1);
}
}
return null;
}
private String getCookiePath(HttpServletRequest request) {
if (this.cookiePath == null) {
return request.getContextPath() + "/";
}
return this.cookiePath;
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.web.http;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* A {@link HttpSessionIdResolver} that uses a header to resolve the session id.
* Specifically, this implementation will allow specifying a header name using
* {@link #HeaderHttpSessionIdResolver(String)}. Convenience factory methods for creating
* instances that use common header names, such as "X-Auth-Token" and
* "Authentication-Info", are available as well.
* <p>
* When a session is created, the HTTP response will have a response header of the
* specified name and the value of the session id. For example:
*
* <pre>
* HTTP/1.1 200 OK
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* The client should now include the session in each request by specifying the same header
* in their request. For example:
*
* <pre>
* GET /messages/ HTTP/1.1
* Host: example.com
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* When the session is invalidated, the server will send an HTTP response that has the
* header name and a blank value. For example:
*
* <pre>
* HTTP/1.1 200 OK
* X-Auth-Token:
* </pre>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public class HeaderHttpSessionIdResolver implements HttpSessionIdResolver {
private static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
private static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info";
private final String headerName;
/**
* Convenience factory to create {@link HeaderHttpSessionIdResolver} that uses
* "X-Auth-Token" header.
* @return the instance configured to use "X-Auth-Token" header
*/
public static HeaderHttpSessionIdResolver xAuthToken() {
return new HeaderHttpSessionIdResolver(HEADER_X_AUTH_TOKEN);
}
/**
* Convenience factory to create {@link HeaderHttpSessionIdResolver} that uses
* "Authentication-Info" header.
* @return the instance configured to use "Authentication-Info" header
*/
public static HeaderHttpSessionIdResolver authenticationInfo() {
return new HeaderHttpSessionIdResolver(HEADER_AUTHENTICATION_INFO);
}
/**
* The name of the header to obtain the session id from.
* @param headerName the name of the header to obtain the session id from.
*/
public HeaderHttpSessionIdResolver(String headerName) {
if (headerName == null) {
throw new IllegalArgumentException("headerName cannot be null");
}
this.headerName = headerName;
}
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
String headerValue = request.getHeader(this.headerName);
return (headerValue != null) ? Collections.singletonList(headerValue)
: Collections.emptyList();
}
@Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId) {
response.setHeader(this.headerName, sessionId);
}
@Override
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, "");
}
}

View File

@@ -0,0 +1,241 @@
/*
* 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.web.http;
import java.time.Duration;
import java.util.Collections;
import java.util.Enumeration;
import java.util.NoSuchElementException;
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;
/**
* Adapts Spring Session's {@link Session} to an {@link HttpSession}.
*
* @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;
private boolean invalidated;
private boolean old;
HttpSessionAdapter(S session, ServletContext servletContext) {
if (session == null) {
throw new IllegalArgumentException("session cannot be null");
}
if (servletContext == null) {
throw new IllegalArgumentException("servletContext cannot be null");
}
this.session = session;
this.servletContext = servletContext;
}
public void setSession(S session) {
this.session = session;
}
public S getSession() {
return this.session;
}
@Override
public long getCreationTime() {
checkState();
return this.session.getCreationTime().toEpochMilli();
}
@Override
public String getId() {
return this.session.getId();
}
@Override
public long getLastAccessedTime() {
checkState();
return this.session.getLastAccessedTime().toEpochMilli();
}
@Override
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
public void setMaxInactiveInterval(int interval) {
this.session.setMaxInactiveInterval(Duration.ofSeconds(interval));
}
@Override
public int getMaxInactiveInterval() {
return (int) this.session.getMaxInactiveInterval().getSeconds();
}
@Override
public HttpSessionContext getSessionContext() {
return NOOP_SESSION_CONTEXT;
}
@Override
public Object getAttribute(String name) {
checkState();
return this.session.getAttribute(name);
}
@Override
public Object getValue(String name) {
return getAttribute(name);
}
@Override
public Enumeration<String> getAttributeNames() {
checkState();
return Collections.enumeration(this.session.getAttributeNames());
}
@Override
public String[] getValueNames() {
checkState();
Set<String> attrs = this.session.getAttributeNames();
return attrs.toArray(new String[0]);
}
@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
public void putValue(String name, Object value) {
setAttribute(name, value);
}
@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
public void removeValue(String name) {
removeAttribute(name);
}
@Override
public void invalidate() {
checkState();
this.invalidated = true;
}
public void setNew(boolean isNew) {
this.old = !isNew;
}
@Override
public boolean isNew() {
checkState();
return !this.old;
}
private void checkState() {
if (this.invalidated) {
throw new IllegalStateException(
"The HttpSession has already be invalidated.");
}
}
private static final HttpSessionContext NOOP_SESSION_CONTEXT = new HttpSessionContext() {
@Override
public HttpSession getSession(String sessionId) {
return null;
}
@Override
public Enumeration<String> getIds() {
return EMPTY_ENUMERATION;
}
};
private static final Enumeration<String> EMPTY_ENUMERATION = new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
throw new NoSuchElementException("a");
}
};
}

View File

@@ -0,0 +1,66 @@
/*
* 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.web.http;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Contract for session id resolution strategies. Allows for session id resolution through
* the request and for sending the session id or expiring the session through the
* response.
*
* @author Rob Winch
* @author Vedran Pavic
* @since 2.0.0
*/
public interface HttpSessionIdResolver {
/**
* Resolve the session ids associated with the provided {@link HttpServletRequest}.
* For example, the session id might come from a cookie or a request header.
* @param request the current request
* @return the session ids
*/
List<String> resolveSessionIds(HttpServletRequest request);
/**
* Send the given session id to the client. This method is invoked when a new session
* is created and should inform a client what the new session id is. For example, it
* might create a new cookie with the session id in it or set an HTTP response header
* with the value of the new session id.
* @param request the current request
* @param response the current response
* @param sessionId the session id
*/
void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId);
/**
* Instruct the client to end the current session. This method is invoked when a
* session is invalidated and should inform a client that the session id is no longer
* valid. For example, it might remove a cookie with the session id in it or set an
* HTTP response header with an empty value indicating to the client to no longer
* submit that session id.
* @param request the current request
* @param response the current response
*/
void expireSession(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -0,0 +1,638 @@
/*
* 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.web.http;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Base class for response wrappers which encapsulate the logic for handling an event when
* the {@link javax.servlet.http.HttpServletResponse} is committed.
*
* @author Rob Winch
* @since 1.0
*/
abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
private final Log logger = LogFactory.getLog(getClass());
private boolean disableOnCommitted;
/**
* The Content-Length response header. If this is greater than 0, then once
* {@link #contentWritten} is larger than or equal the response is considered
* committed.
*/
private long contentLength;
/**
* The size of data written to the response body.
*/
private long contentWritten;
/**
* Create a new {@link OnCommittedResponseWrapper}.
* @param response the response to be wrapped
*/
OnCommittedResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public void addHeader(String name, String value) {
if ("Content-Length".equalsIgnoreCase(name)) {
setContentLength(Long.parseLong(value));
}
super.addHeader(name, value);
}
@Override
public void setContentLength(int len) {
setContentLength((long) len);
super.setContentLength(len);
}
private void setContentLength(long len) {
this.contentLength = len;
checkContentLength(0);
}
/**
* Invoke this method to disable invoking
* {@link OnCommittedResponseWrapper#onResponseCommitted()} when the
* {@link javax.servlet.http.HttpServletResponse} is committed. This can be useful in
* the event that Async Web Requests are made.
*/
public void disableOnResponseCommitted() {
this.disableOnCommitted = true;
}
/**
* Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse}
* being committed.
*/
protected abstract void onResponseCommitted();
/**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* before calling the superclass <code>sendError()</code>.
* @param sc the error status code
*/
@Override
public final void sendError(int sc) throws IOException {
doOnResponseCommitted();
super.sendError(sc);
}
/**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* before calling the superclass <code>sendError()</code>.
* @param sc the error status code
*/
@Override
public final void sendError(int sc, String msg) throws IOException {
doOnResponseCommitted();
super.sendError(sc, msg);
}
/**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* before calling the superclass <code>sendRedirect()</code>.
* @param location the redirect URL location
*/
@Override
public final void sendRedirect(String location) throws IOException {
doOnResponseCommitted();
super.sendRedirect(location);
}
/**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* before calling the calling <code>getOutputStream().close()</code> or
* <code>getOutputStream().flush()</code>.
* @throws IOException if an input or output exception occurred
*/
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new SaveContextServletOutputStream(super.getOutputStream());
}
/**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* before calling the <code>getWriter().close()</code> or
* <code>getWriter().flush()</code>.
* @throws IOException if an input or output exception occurred
*/
@Override
public PrintWriter getWriter() throws IOException {
return new SaveContextPrintWriter(super.getWriter());
}
/**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* before calling the superclass <code>flushBuffer()</code>.
* @throws IOException if an input or output exception occurred
*/
@Override
public void flushBuffer() throws IOException {
doOnResponseCommitted();
super.flushBuffer();
}
private void trackContentLength(boolean content) {
checkContentLength(content ? 4 : 5); // TODO Localization
}
private void trackContentLength(char content) {
checkContentLength(1);
}
private void trackContentLength(Object content) {
trackContentLength(String.valueOf(content));
}
private void trackContentLength(byte[] content) {
checkContentLength((content != null) ? content.length : 0);
}
private void trackContentLength(char[] content) {
checkContentLength((content != null) ? content.length : 0);
}
private void trackContentLength(int content) {
trackContentLength(String.valueOf(content));
}
private void trackContentLength(float content) {
trackContentLength(String.valueOf(content));
}
private void trackContentLength(double content) {
trackContentLength(String.valueOf(content));
}
private void trackContentLengthLn() {
trackContentLength("\r\n");
}
private void trackContentLength(String content) {
checkContentLength(content.length());
}
/**
* Adds the contentLengthToWrite to the total contentWritten size and checks to see if
* the response should be written.
*
* @param contentLengthToWrite the size of the content that is about to be written.
*/
private void checkContentLength(long contentLengthToWrite) {
this.contentWritten += contentLengthToWrite;
boolean isBodyFullyWritten = this.contentLength > 0
&& this.contentWritten >= this.contentLength;
int bufferSize = getBufferSize();
boolean requiresFlush = bufferSize > 0 && this.contentWritten >= bufferSize;
if (isBodyFullyWritten || requiresFlush) {
doOnResponseCommitted();
}
}
/**
* Calls <code>onResponseCommmitted()</code> with the current contents as long as
* {@link #disableOnResponseCommitted()} was not invoked.
*/
private void doOnResponseCommitted() {
if (!this.disableOnCommitted) {
onResponseCommitted();
disableOnResponseCommitted();
}
}
/**
* Ensures {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
* calling the prior to methods that commit the response. We delegate all methods to
* the original {@link java.io.PrintWriter} to ensure that the behavior is as close to
* the original {@link java.io.PrintWriter} as possible. See SEC-2039
* @author Rob Winch
*/
private class SaveContextPrintWriter extends PrintWriter {
private final PrintWriter delegate;
SaveContextPrintWriter(PrintWriter delegate) {
super(delegate);
this.delegate = delegate;
}
@Override
public void flush() {
doOnResponseCommitted();
this.delegate.flush();
}
@Override
public void close() {
doOnResponseCommitted();
this.delegate.close();
}
@Override
public boolean equals(Object obj) {
return this.delegate.equals(obj);
}
@Override
public int hashCode() {
return this.delegate.hashCode();
}
@Override
public String toString() {
return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
}
@Override
public boolean checkError() {
return this.delegate.checkError();
}
@Override
public void write(int c) {
trackContentLength(c);
this.delegate.write(c);
}
@Override
public void write(char[] buf, int off, int len) {
checkContentLength(len);
this.delegate.write(buf, off, len);
}
@Override
public void write(char[] buf) {
trackContentLength(buf);
this.delegate.write(buf);
}
@Override
public void write(String s, int off, int len) {
checkContentLength(len);
this.delegate.write(s, off, len);
}
@Override
public void write(String s) {
trackContentLength(s);
this.delegate.write(s);
}
@Override
public void print(boolean b) {
trackContentLength(b);
this.delegate.print(b);
}
@Override
public void print(char c) {
trackContentLength(c);
this.delegate.print(c);
}
@Override
public void print(int i) {
trackContentLength(i);
this.delegate.print(i);
}
@Override
public void print(long l) {
trackContentLength(l);
this.delegate.print(l);
}
@Override
public void print(float f) {
trackContentLength(f);
this.delegate.print(f);
}
@Override
public void print(double d) {
trackContentLength(d);
this.delegate.print(d);
}
@Override
public void print(char[] s) {
trackContentLength(s);
this.delegate.print(s);
}
@Override
public void print(String s) {
trackContentLength(s);
this.delegate.print(s);
}
@Override
public void print(Object obj) {
trackContentLength(obj);
this.delegate.print(obj);
}
@Override
public void println() {
trackContentLengthLn();
this.delegate.println();
}
@Override
public void println(boolean x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(char x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(int x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(long x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(float x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(double x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(char[] x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(String x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public void println(Object x) {
trackContentLength(x);
trackContentLengthLn();
this.delegate.println(x);
}
@Override
public PrintWriter printf(String format, Object... args) {
return this.delegate.printf(format, args);
}
@Override
public PrintWriter printf(Locale l, String format, Object... args) {
return this.delegate.printf(l, format, args);
}
@Override
public PrintWriter format(String format, Object... args) {
return this.delegate.format(format, args);
}
@Override
public PrintWriter format(Locale l, String format, Object... args) {
return this.delegate.format(l, format, args);
}
@Override
public PrintWriter append(CharSequence csq) {
checkContentLength(csq.length());
return this.delegate.append(csq);
}
@Override
public PrintWriter append(CharSequence csq, int start, int end) {
checkContentLength(end - start);
return this.delegate.append(csq, start, end);
}
@Override
public PrintWriter append(char c) {
trackContentLength(c);
return this.delegate.append(c);
}
}
/**
* Ensures{@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
* calling methods that commit the response. We delegate all methods to the original
* {@link javax.servlet.ServletOutputStream} to ensure that the behavior is as close
* to the original {@link javax.servlet.ServletOutputStream} as possible. See SEC-2039
*
* @author Rob Winch
*/
private class SaveContextServletOutputStream extends ServletOutputStream {
private final ServletOutputStream delegate;
SaveContextServletOutputStream(ServletOutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
trackContentLength(b);
this.delegate.write(b);
}
@Override
public void flush() throws IOException {
doOnResponseCommitted();
this.delegate.flush();
}
@Override
public void close() throws IOException {
doOnResponseCommitted();
this.delegate.close();
}
@Override
public boolean equals(Object obj) {
return this.delegate.equals(obj);
}
@Override
public int hashCode() {
return this.delegate.hashCode();
}
@Override
public void print(boolean b) throws IOException {
trackContentLength(b);
this.delegate.print(b);
}
@Override
public void print(char c) throws IOException {
trackContentLength(c);
this.delegate.print(c);
}
@Override
public void print(double d) throws IOException {
trackContentLength(d);
this.delegate.print(d);
}
@Override
public void print(float f) throws IOException {
trackContentLength(f);
this.delegate.print(f);
}
@Override
public void print(int i) throws IOException {
trackContentLength(i);
this.delegate.print(i);
}
@Override
public void print(long l) throws IOException {
trackContentLength(l);
this.delegate.print(l);
}
@Override
public void print(String s) throws IOException {
trackContentLength(s);
this.delegate.print(s);
}
@Override
public void println() throws IOException {
trackContentLengthLn();
this.delegate.println();
}
@Override
public void println(boolean b) throws IOException {
trackContentLength(b);
trackContentLengthLn();
this.delegate.println(b);
}
@Override
public void println(char c) throws IOException {
trackContentLength(c);
trackContentLengthLn();
this.delegate.println(c);
}
@Override
public void println(double d) throws IOException {
trackContentLength(d);
trackContentLengthLn();
this.delegate.println(d);
}
@Override
public void println(float f) throws IOException {
trackContentLength(f);
trackContentLengthLn();
this.delegate.println(f);
}
@Override
public void println(int i) throws IOException {
trackContentLength(i);
trackContentLengthLn();
this.delegate.println(i);
}
@Override
public void println(long l) throws IOException {
trackContentLength(l);
trackContentLengthLn();
this.delegate.println(l);
}
@Override
public void println(String s) throws IOException {
trackContentLength(s);
trackContentLengthLn();
this.delegate.println(s);
}
@Override
public void write(byte[] b) throws IOException {
trackContentLength(b);
this.delegate.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
checkContentLength(len);
this.delegate.write(b, off, len);
}
@Override
public String toString() {
return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
}
@Override
public boolean isReady() {
return this.delegate.isReady();
}
@Override
public void setWriteListener(WriteListener writeListener) {
this.delegate.setWriteListener(writeListener);
}
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2014-2019 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.web.http;
import java.io.IOException;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Allows for easily ensuring that a request is only invoked once per request. This is a
* simplified version of spring-web's OncePerRequestFilter and copied to reduce the foot
* print required to use the session support.
*
* @author Rob Winch
* @since 1.0
*/
abstract class OncePerRequestFilter implements Filter {
/**
* Suffix that gets appended to the filter name for the "already filtered" request
* attribute.
*/
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
private String alreadyFilteredAttributeName = getClass().getName()
.concat(ALREADY_FILTERED_SUFFIX);
/**
* This {@code doFilter} implementation stores a request attribute for
* "already filtered", proceeding without filtering again if the attribute is already
* there.
* @param request the request
* @param response the response
* @param filterChain the filter chain
* @throws ServletException if request is not HTTP request
* @throws IOException in case of I/O operation exception
*/
@Override
public final void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!(request instanceof HttpServletRequest)
|| !(response instanceof HttpServletResponse)) {
throw new ServletException(
"OncePerRequestFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String alreadyFilteredAttributeName = this.alreadyFilteredAttributeName;
alreadyFilteredAttributeName = updateForErrorDispatch(
alreadyFilteredAttributeName, request);
boolean hasAlreadyFilteredAttribute = request
.getAttribute(alreadyFilteredAttributeName) != null;
if (hasAlreadyFilteredAttribute) {
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else {
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
private String updateForErrorDispatch(String alreadyFilteredAttributeName,
ServletRequest request) {
// Jetty does ERROR dispatch within sendError, so request attribute is still present
// Use a separate attribute for ERROR dispatches
if (DispatcherType.ERROR.equals(request.getDispatcherType())
&& request.getAttribute(alreadyFilteredAttributeName) != null) {
return alreadyFilteredAttributeName + ".ERROR";
}
return alreadyFilteredAttributeName;
}
/**
* Same contract as for {@code doFilter}, but guaranteed to be just invoked once per
* request within a single request thread.
* <p>
* Provides HttpServletRequest and HttpServletResponse arguments instead of the
* default ServletRequest and ServletResponse ones.
*
* @param request the request
* @param response the response
* @param filterChain the FilterChain
* @throws ServletException thrown when a non-I/O exception has occurred
* @throws IOException thrown when an I/O exception of some sort has occurred
* @see Filter#doFilter
*/
protected abstract void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException;
@Override
public void init(FilterConfig config) {
}
@Override
public void destroy() {
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.
* 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.web.http;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.springframework.context.ApplicationListener;
import org.springframework.session.Session;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.web.context.ServletContextAware;
/**
* Receives {@link SessionDestroyedEvent} and {@link SessionCreatedEvent} and translates
* them into {@link HttpSessionEvent} and submits the {@link HttpSessionEvent} to every
* registered {@link HttpSessionListener}.
*
* @author Rob Winch
* @since 1.1
*/
public class SessionEventHttpSessionListenerAdapter
implements ApplicationListener<AbstractSessionEvent>, ServletContextAware {
private final List<HttpSessionListener> listeners;
private ServletContext context;
public SessionEventHttpSessionListenerAdapter(List<HttpSessionListener> listeners) {
super();
this.listeners = listeners;
}
/*
* (non-Javadoc)
*
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.
* springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(AbstractSessionEvent event) {
if (this.listeners.isEmpty()) {
return;
}
HttpSessionEvent httpSessionEvent = createHttpSessionEvent(event);
for (HttpSessionListener listener : this.listeners) {
if (event instanceof SessionDestroyedEvent) {
listener.sessionDestroyed(httpSessionEvent);
}
else if (event instanceof SessionCreatedEvent) {
listener.sessionCreated(httpSessionEvent);
}
}
}
private HttpSessionEvent createHttpSessionEvent(AbstractSessionEvent event) {
Session session = event.getSession();
HttpSession httpSession = new HttpSessionAdapter<>(session,
this.context);
return new HttpSessionEvent(httpSession);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet
* .ServletContext)
*/
@Override
public void setServletContext(ServletContext servletContext) {
this.context = servletContext;
}
}

View File

@@ -0,0 +1,433 @@
/*
* Copyright 2014-2019 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.web.http;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.Order;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
/**
* Switches the {@link javax.servlet.http.HttpSession} implementation to be backed by a
* {@link org.springframework.session.Session}.
*
* The {@link SessionRepositoryFilter} wraps the
* {@link javax.servlet.http.HttpServletRequest} and overrides the methods to get an
* {@link javax.servlet.http.HttpSession} to be backed by a
* {@link org.springframework.session.Session} returned by the
* {@link org.springframework.session.SessionRepository}.
*
* The {@link SessionRepositoryFilter} uses a {@link HttpSessionIdResolver} (default
* {@link CookieHttpSessionIdResolver}) to bridge logic between an
* {@link javax.servlet.http.HttpSession} and the
* {@link org.springframework.session.Session} abstraction. Specifically:
*
* <ul>
* <li>The session id is looked up using
* {@link HttpSessionIdResolver#resolveSessionIds(javax.servlet.http.HttpServletRequest)}
* . The default is to look in a cookie named SESSION.</li>
* <li>The session id of newly created {@link org.springframework.session.Session} is sent
* to the client using
* <li>The client is notified that the session id is no longer valid with
* {@link HttpSessionIdResolver#expireSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
* </li>
* </ul>
*
* <p>
* The SessionRepositoryFilter must be placed before any Filter that access the
* HttpSession or that might commit the response to ensure the session is overridden and
* persisted properly.
* </p>
*
* @param <S> the {@link Session} type.
* @since 1.0
* @author Rob Winch
* @author Vedran Pavic
* @author Josh Cummings
*/
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
private static final String SESSION_LOGGER_NAME = SessionRepositoryFilter.class
.getName().concat(".SESSION_LOGGER");
private static final Log SESSION_LOGGER = LogFactory.getLog(SESSION_LOGGER_NAME);
/**
* The session repository request attribute name.
*/
public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class
.getName();
/**
* Invalid session id (not backed by the session repository) request attribute name.
*/
public static final String INVALID_SESSION_ID_ATTR = SESSION_REPOSITORY_ATTR
+ ".invalidSessionId";
private static final String CURRENT_SESSION_ATTR = SESSION_REPOSITORY_ATTR
+ ".CURRENT_SESSION";
/**
* The default filter order.
*/
public static final int DEFAULT_ORDER = Integer.MIN_VALUE + 50;
private final SessionRepository<S> sessionRepository;
private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();
/**
* Creates a new instance.
*
* @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
*/
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
if (sessionRepository == null) {
throw new IllegalArgumentException("sessionRepository cannot be null");
}
this.sessionRepository = sessionRepository;
}
/**
* Sets the {@link HttpSessionIdResolver} to be used. The default is a
* {@link CookieHttpSessionIdResolver}.
*
* @param httpSessionIdResolver the {@link HttpSessionIdResolver} to use. Cannot be
* null.
*/
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
if (httpSessionIdResolver == null) {
throw new IllegalArgumentException("httpSessionIdResolver cannot be null");
}
this.httpSessionIdResolver = httpSessionIdResolver;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession();
}
}
/**
* Allows ensuring that the session is saved if the response is committed.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryResponseWrapper
extends OnCommittedResponseWrapper {
private final SessionRepositoryRequestWrapper request;
/**
* Create a new {@link SessionRepositoryResponseWrapper}.
* @param request the request to be wrapped
* @param response the response to be wrapped
*/
SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request,
HttpServletResponse response) {
super(response);
if (request == null) {
throw new IllegalArgumentException("request cannot be null");
}
this.request = request;
}
@Override
protected void onResponseCommitted() {
this.request.commitSession();
}
}
/**
* A {@link javax.servlet.http.HttpServletRequest} that retrieves the
* {@link javax.servlet.http.HttpSession} using a
* {@link org.springframework.session.SessionRepository}.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryRequestWrapper
extends HttpServletRequestWrapper {
private final HttpServletResponse response;
private S requestedSession;
private boolean requestedSessionCached;
private String requestedSessionId;
private Boolean requestedSessionIdValid;
private boolean requestedSessionInvalidated;
private SessionRepositoryRequestWrapper(HttpServletRequest request,
HttpServletResponse response) {
super(request);
this.response = response;
}
/**
* Uses the {@link HttpSessionIdResolver} to write the session id to the response
* and persist the Session.
*/
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this,
this.response);
}
}
else {
S session = wrappedSession.getSession();
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.save(session);
String sessionId = session.getId();
if (!isRequestedSessionIdValid()
|| !sessionId.equals(getRequestedSessionId())) {
SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this,
this.response, sessionId);
}
}
}
@SuppressWarnings("unchecked")
private HttpSessionWrapper getCurrentSession() {
return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
}
private void setCurrentSession(HttpSessionWrapper currentSession) {
if (currentSession == null) {
removeAttribute(CURRENT_SESSION_ATTR);
}
else {
setAttribute(CURRENT_SESSION_ATTR, currentSession);
}
}
@Override
@SuppressWarnings("unused")
public String changeSessionId() {
HttpSession session = getSession(false);
if (session == null) {
throw new IllegalStateException(
"Cannot change session ID. There is no session associated with this request.");
}
return getCurrentSession().getSession().changeSessionId();
}
@Override
public boolean isRequestedSessionIdValid() {
if (this.requestedSessionIdValid == null) {
S requestedSession = getRequestedSession();
if (requestedSession != null) {
requestedSession.setLastAccessedTime(Instant.now());
}
return isRequestedSessionIdValid(requestedSession);
}
return this.requestedSessionIdValid;
}
private boolean isRequestedSessionIdValid(S session) {
if (this.requestedSessionIdValid == null) {
this.requestedSessionIdValid = session != null;
}
return this.requestedSessionIdValid;
}
private boolean isInvalidateClientSession() {
return getCurrentSession() == null && this.requestedSessionInvalidated;
}
@Override
public HttpSessionWrapper getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
S requestedSession = getRequestedSession();
if (requestedSession != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
currentSession.setNew(false);
setCurrentSession(currentSession);
return currentSession;
}
}
else {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
if (!create) {
return null;
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
+ SESSION_LOGGER_NAME,
new RuntimeException(
"For debugging purposes only (not an error)"));
}
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
@Override
public HttpSessionWrapper getSession() {
return getSession(true);
}
@Override
public String getRequestedSessionId() {
if (this.requestedSessionId == null) {
getRequestedSession();
}
return this.requestedSessionId;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
RequestDispatcher requestDispatcher = super.getRequestDispatcher(path);
return new SessionCommittingRequestDispatcher(requestDispatcher);
}
private S getRequestedSession() {
if (!this.requestedSessionCached) {
List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
.resolveSessionIds(this);
for (String sessionId : sessionIds) {
if (this.requestedSessionId == null) {
this.requestedSessionId = sessionId;
}
S session = SessionRepositoryFilter.this.sessionRepository
.findById(sessionId);
if (session != null) {
this.requestedSession = session;
this.requestedSessionId = sessionId;
break;
}
}
this.requestedSessionCached = true;
}
return this.requestedSession;
}
private void clearRequestedSessionCache() {
this.requestedSessionCached = false;
this.requestedSession = null;
this.requestedSessionId = null;
}
/**
* Allows creating an HttpSession from a Session instance.
*
* @author Rob Winch
* @since 1.0
*/
private final class HttpSessionWrapper extends HttpSessionAdapter<S> {
HttpSessionWrapper(S session, ServletContext servletContext) {
super(session, servletContext);
}
@Override
public void invalidate() {
super.invalidate();
SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
setCurrentSession(null);
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
}
}
/**
* Ensures session is committed before issuing an include.
*
* @since 1.3.4
*/
private final class SessionCommittingRequestDispatcher
implements RequestDispatcher {
private final RequestDispatcher delegate;
SessionCommittingRequestDispatcher(RequestDispatcher delegate) {
this.delegate = delegate;
}
@Override
public void forward(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
this.delegate.forward(request, response);
}
@Override
public void include(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
SessionRepositoryRequestWrapper.this.commitSession();
this.delegate.include(request, response);
}
}
}
}

View File

@@ -0,0 +1,350 @@
/*
* 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.web.server.session;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.WebSessionStore;
/**
* The {@link WebSessionStore} implementation that provides the {@link WebSession}
* implementation backed by a {@link Session} returned by the
* {@link ReactiveSessionRepository}.
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @author Vedran Pavic
* @since 2.0
*/
public class SpringSessionWebSessionStore<S extends Session> implements WebSessionStore {
private final ReactiveSessionRepository<S> sessions;
private Clock clock = Clock.system(ZoneOffset.UTC);
public SpringSessionWebSessionStore(ReactiveSessionRepository<S> reactiveSessionRepository) {
Assert.notNull(reactiveSessionRepository, "reactiveSessionRepository cannot be null");
this.sessions = reactiveSessionRepository;
}
/**
* Configure the {@link Clock} to use to set lastAccessTime on every created
* session and to calculate if it is expired.
* <p>This may be useful to align to different timezone or to set the clock
* back in a test, e.g. {@code Clock.offset(clock, Duration.ofMinutes(-31))}
* in order to simulate session expiration.
* <p>By default this is {@code Clock.system(ZoneId.of("GMT"))}.
* @param clock the clock to use
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "clock cannot be null");
this.clock = clock;
}
@Override
public Mono<WebSession> createWebSession() {
return this.sessions.createSession().map(this::createSession);
}
@Override
public Mono<WebSession> updateLastAccessTime(WebSession session) {
@SuppressWarnings("unchecked")
SpringSessionWebSession springSessionWebSession = (SpringSessionWebSession) session;
springSessionWebSession.session.setLastAccessedTime(this.clock.instant());
return Mono.just(session);
}
@Override
public Mono<WebSession> retrieveSession(String sessionId) {
return this.sessions.findById(sessionId)
.doOnNext((session) -> session.setLastAccessedTime(this.clock.instant()))
.map(this::existingSession);
}
@Override
public Mono<Void> removeSession(String sessionId) {
return this.sessions.deleteById(sessionId);
}
private SpringSessionWebSession createSession(S session) {
return new SpringSessionWebSession(session, State.NEW);
}
private SpringSessionWebSession existingSession(S session) {
return new SpringSessionWebSession(session, State.STARTED);
}
/**
* Adapts Spring Session's {@link Session} to a {@link WebSession}.
*/
private class SpringSessionWebSession implements WebSession {
private final S session;
private final Map<String, Object> attributes;
private AtomicReference<State> state = new AtomicReference<>();
SpringSessionWebSession(S session, State state) {
Assert.notNull(session, "session cannot be null");
this.session = session;
this.attributes = new SpringSessionMap(session);
this.state.set(state);
}
@Override
public String getId() {
return this.session.getId();
}
@Override
public Mono<Void> changeSessionId() {
return Mono.defer(() -> {
this.session
.changeSessionId();
return save();
});
}
@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}
@Override
public void start() {
this.state.compareAndSet(State.NEW, State.STARTED);
}
@Override
public boolean isStarted() {
State value = this.state.get();
return (State.STARTED.equals(value)
|| (State.NEW.equals(value) && !getAttributes().isEmpty()));
}
@Override
public Mono<Void> invalidate() {
this.state.set(State.EXPIRED);
return SpringSessionWebSessionStore.this.sessions.deleteById(this.session.getId());
}
@Override
public Mono<Void> save() {
return SpringSessionWebSessionStore.this.sessions.save(this.session);
}
@Override
public boolean isExpired() {
if (this.state.get().equals(State.EXPIRED)) {
return true;
}
if (this.session.isExpired()) {
this.state.set(State.EXPIRED);
return true;
}
return false;
}
@Override
public Instant getCreationTime() {
return this.session.getCreationTime();
}
@Override
public Instant getLastAccessTime() {
return this.session.getLastAccessedTime();
}
@Override
public Duration getMaxIdleTime() {
return this.session.getMaxInactiveInterval();
}
@Override
public void setMaxIdleTime(Duration maxIdleTime) {
this.session.setMaxInactiveInterval(maxIdleTime);
}
}
private enum State {
NEW, STARTED, EXPIRED
}
private static class SpringSessionMap implements Map<String, Object> {
private final Session session;
private final Collection<Object> values = new SessionValues();
SpringSessionMap(Session session) {
this.session = session;
}
@Override
public int size() {
return this.session.getAttributeNames().size();
}
@Override
public boolean isEmpty() {
return this.session.getAttributeNames().isEmpty();
}
@Override
public boolean containsKey(Object key) {
return key instanceof String
&& this.session.getAttributeNames().contains(key);
}
@Override
public boolean containsValue(Object value) {
return this.session.getAttributeNames().stream()
.anyMatch((attrName) -> this.session.getAttribute(attrName) != null);
}
@Override
@Nullable
public Object get(Object key) {
if (key instanceof String) {
return this.session.getAttribute((String) key);
}
return null;
}
@Override
public Object put(String key, Object value) {
Object original = this.session.getAttribute(key);
this.session.setAttribute(key, value);
return original;
}
@Override
@Nullable
public Object remove(Object key) {
if (key instanceof String) {
String attrName = (String) key;
Object original = this.session.getAttribute(attrName);
this.session.removeAttribute(attrName);
return original;
}
return null;
}
@Override
public void putAll(Map<? extends String, ?> m) {
for (Entry<? extends String, ?> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
for (String attrName : this.session.getAttributeNames()) {
remove(attrName);
}
}
@Override
public Set<String> keySet() {
return this.session.getAttributeNames();
}
@Override
public Collection<Object> values() {
return this.values;
}
@Override
public Set<Entry<String, Object>> entrySet() {
Set<String> attrNames = keySet();
Set<Entry<String, Object>> entries = new HashSet<>(attrNames.size());
for (String attrName : attrNames) {
Object value = this.session.getAttribute(attrName);
entries.add(new AbstractMap.SimpleEntry<>(attrName, value));
}
return Collections.unmodifiableSet(entries);
}
private class SessionValues extends AbstractCollection<Object> {
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private Iterator<Entry<String, Object>> i = entrySet().iterator();
@Override
public boolean hasNext() {
return this.i.hasNext();
}
@Override
public Object next() {
return this.i.next().getValue();
}
@Override
public void remove() {
this.i.remove();
}
};
}
@Override
public int size() {
return SpringSessionMap.this.size();
}
@Override
public boolean isEmpty() {
return SpringSessionMap.this.isEmpty();
}
@Override
public void clear() {
SpringSessionMap.this.clear();
}
@Override
public boolean contains(Object v) {
return SpringSessionMap.this.containsValue(v);
}
}
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
* 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.
*/
/**
* Spring Session reactive web support.
*/
@NonNullApi
package org.springframework.session.web.server.session;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,169 @@
/*
* 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.web.socket.config.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.socket.handler.WebSocketConnectHandlerDecoratorFactory;
import org.springframework.session.web.socket.handler.WebSocketRegistryListener;
import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.StompWebSocketEndpointRegistration;
import org.springframework.web.socket.config.annotation.WebMvcStompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.util.UrlPathHelper;
/**
* Eases configuration of Web Socket and Spring Session integration.
*
* <p>
* The configuration:
* </p>
* <ul>
* <li>Ensures the {@link Session} is kept alive on incoming web socket messages.</li>
* <li>Ensures that Web Socket Sessions are destroyed when a {@link Session} is terminated
* </li>
* </ul>
*
* <p>
* Example usage
* </p>
*
* <code>
* {@literal @Configuration}
* {@literal @EnableScheduling}
* {@literal @EnableWebSocketMessageBroker}
* {@literal public class WebSocketConfig<S extends Session> extends AbstractSessionWebSocketMessageBrokerConfigurer<S>} {
*
* {@literal @Override}
* protected void configureStompEndpoints(StompEndpointRegistry registry) {
* registry.addEndpoint("/messages")
* .withSockJS();
* }
*
* {@literal @Override}
* public void configureMessageBroker(MessageBrokerRegistry registry) {
* registry.enableSimpleBroker("/queue/", "/topic/");
* registry.setApplicationDestinationPrefixes("/app");
* }
* }
* </code>
*
* @param <S> the type of Session
* @author Rob Winch
* @since 1.0
*/
public abstract class AbstractSessionWebSocketMessageBrokerConfigurer<S extends Session>
implements WebSocketMessageBrokerConfigurer {
@Autowired
@SuppressWarnings("rawtypes")
private SessionRepository sessionRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(sessionRepositoryInterceptor());
}
@Override
public final void registerStompEndpoints(StompEndpointRegistry registry) {
if (registry instanceof WebMvcStompEndpointRegistry) {
WebMvcStompEndpointRegistry mvcRegistry = (WebMvcStompEndpointRegistry) registry;
configureStompEndpoints(new SessionStompEndpointRegistry(mvcRegistry,
sessionRepositoryInterceptor()));
}
}
/**
* Register STOMP endpoints mapping each to a specific URL and (optionally) enabling
* and configuring SockJS fallback options with a
* {@link SessionRepositoryMessageInterceptor} automatically added as an interceptor.
*
* @param registry the {@link StompEndpointRegistry} which automatically has a
* {@link SessionRepositoryMessageInterceptor} added to it.
*/
protected abstract void configureStompEndpoints(StompEndpointRegistry registry);
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.addDecoratorFactory(wsConnectHandlerDecoratorFactory());
}
@Bean
public WebSocketRegistryListener webSocketRegistryListener() {
return new WebSocketRegistryListener();
}
@Bean
public WebSocketConnectHandlerDecoratorFactory wsConnectHandlerDecoratorFactory() {
return new WebSocketConnectHandlerDecoratorFactory(this.eventPublisher);
}
@Bean
@SuppressWarnings("unchecked")
public SessionRepositoryMessageInterceptor<S> sessionRepositoryInterceptor() {
return new SessionRepositoryMessageInterceptor<>(this.sessionRepository);
}
/**
* A {@link StompEndpointRegistry} that applies {@link HandshakeInterceptor}.
*/
static class SessionStompEndpointRegistry implements StompEndpointRegistry {
private final WebMvcStompEndpointRegistry registry;
private final HandshakeInterceptor interceptor;
SessionStompEndpointRegistry(WebMvcStompEndpointRegistry registry,
HandshakeInterceptor interceptor) {
this.registry = registry;
this.interceptor = interceptor;
}
@Override
public StompWebSocketEndpointRegistration addEndpoint(String... paths) {
StompWebSocketEndpointRegistration endpoints = this.registry
.addEndpoint(paths);
endpoints.addInterceptors(this.interceptor);
return endpoints;
}
@Override
public void setOrder(int order) {
this.registry.setOrder(order);
}
@Override
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.registry.setUrlPathHelper(urlPathHelper);
}
@Override
public WebMvcStompEndpointRegistry setErrorHandler(
StompSubProtocolErrorHandler errorHandler) {
return this.registry.setErrorHandler(errorHandler);
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.socket.events;
import org.springframework.context.ApplicationEvent;
import org.springframework.session.web.socket.handler.WebSocketConnectHandlerDecoratorFactory;
import org.springframework.session.web.socket.handler.WebSocketRegistryListener;
import org.springframework.web.socket.WebSocketSession;
/**
* Similar to Spring {@link org.springframework.web.socket.messaging.SessionConnectEvent}
* except that it provides access to the {@link WebSocketSession} to allow mapping the
* Spring Session to the {@link WebSocketSession}.
*
* @author Rob Winch
* @since 1.0
* @see WebSocketRegistryListener
* @see WebSocketConnectHandlerDecoratorFactory
*/
@SuppressWarnings("serial")
public class SessionConnectEvent extends ApplicationEvent {
private final WebSocketSession webSocketSession;
public SessionConnectEvent(Object source, WebSocketSession webSocketSession) {
super(source);
this.webSocketSession = webSocketSession;
}
public WebSocketSession getWebSocketSession() {
return this.webSocketSession;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.socket.handler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.session.Session;
import org.springframework.session.web.socket.events.SessionConnectEvent;
import org.springframework.util.Assert;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
/**
* Ensures that a {@link SessionConnectEvent} is published in
* {@link WebSocketHandler#afterConnectionEstablished(WebSocketSession)}. This is
* necessary so that the {@link WebSocketSession} can be mapped to the corresponding
* Spring {@link Session} to terminate any {@link WebSocketSession} associated with a
* Spring {@link Session} that was destroyed.
*
* @author Rob Winch
* @since 1.0
*
* @see WebSocketRegistryListener
*/
public final class WebSocketConnectHandlerDecoratorFactory
implements WebSocketHandlerDecoratorFactory {
private static final Log logger = LogFactory
.getLog(WebSocketConnectHandlerDecoratorFactory.class);
private final ApplicationEventPublisher eventPublisher;
/**
* Creates a new instance.
*
* @param eventPublisher the {@link ApplicationEventPublisher} to use. Cannot be null.
*/
public WebSocketConnectHandlerDecoratorFactory(
ApplicationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
this.eventPublisher = eventPublisher;
}
@Override
public WebSocketHandler decorate(WebSocketHandler handler) {
return new SessionWebSocketHandler(handler);
}
private final class SessionWebSocketHandler extends WebSocketHandlerDecorator {
SessionWebSocketHandler(WebSocketHandler delegate) {
super(delegate);
}
@Override
public void afterConnectionEstablished(WebSocketSession wsSession)
throws Exception {
super.afterConnectionEstablished(wsSession);
publishEvent(new SessionConnectEvent(this, wsSession));
}
private void publishEvent(ApplicationEvent event) {
try {
WebSocketConnectHandlerDecoratorFactory.this.eventPublisher
.publishEvent(event);
}
catch (Throwable ex) {
logger.error("Error publishing " + event + ".", ex);
}
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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.web.socket.handler;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.web.socket.events.SessionConnectEvent;
import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
/**
* <p>
* Keeps track of mapping the Spring Session ID to the {@link WebSocketSession} and
* ensuring when a {@link SessionDestroyedEvent} is fired that the
* {@link WebSocketSession} is closed.
* </p>
*
*
* @author Rob Winch
* @author Mark Anderson
* @since 1.0
*/
public final class WebSocketRegistryListener
implements ApplicationListener<ApplicationEvent> {
private static final Log logger = LogFactory.getLog(WebSocketRegistryListener.class);
static final CloseStatus SESSION_EXPIRED_STATUS = new CloseStatus(
CloseStatus.POLICY_VIOLATION.getCode(),
"This connection was established under an authenticated HTTP Session that has expired");
private final ConcurrentHashMap<String, Map<String, WebSocketSession>> httpSessionIdToWsSessions = new ConcurrentHashMap<>();
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof SessionDestroyedEvent) {
SessionDestroyedEvent e = (SessionDestroyedEvent) event;
closeWsSessions(e.getSessionId());
}
else if (event instanceof SessionConnectEvent) {
SessionConnectEvent e = (SessionConnectEvent) event;
afterConnectionEstablished(e.getWebSocketSession());
}
else if (event instanceof SessionDisconnectEvent) {
SessionDisconnectEvent e = (SessionDisconnectEvent) event;
Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor
.getSessionAttributes(e.getMessage().getHeaders());
String httpSessionId = (sessionAttributes != null)
? SessionRepositoryMessageInterceptor.getSessionId(sessionAttributes)
: null;
afterConnectionClosed(httpSessionId, e.getSessionId());
}
}
private void afterConnectionEstablished(WebSocketSession wsSession) {
Principal principal = wsSession.getPrincipal();
if (principal == null) {
return;
}
String httpSessionId = getHttpSessionId(wsSession);
registerWsSession(httpSessionId, wsSession);
}
private String getHttpSessionId(WebSocketSession wsSession) {
Map<String, Object> attributes = wsSession.getAttributes();
return SessionRepositoryMessageInterceptor.getSessionId(attributes);
}
private void afterConnectionClosed(String httpSessionId, String wsSessionId) {
if (httpSessionId == null) {
return;
}
Map<String, WebSocketSession> sessions = this.httpSessionIdToWsSessions
.get(httpSessionId);
if (sessions != null) {
boolean result = sessions.remove(wsSessionId) != null;
if (logger.isDebugEnabled()) {
logger.debug("Removal of " + wsSessionId + " was " + result);
}
if (sessions.isEmpty()) {
this.httpSessionIdToWsSessions.remove(httpSessionId);
if (logger.isDebugEnabled()) {
logger.debug("Removed the corresponding HTTP Session for "
+ wsSessionId + " since it contained no WebSocket mappings");
}
}
}
}
private void registerWsSession(String httpSessionId, WebSocketSession wsSession) {
Map<String, WebSocketSession> sessions = this.httpSessionIdToWsSessions
.get(httpSessionId);
if (sessions == null) {
sessions = new ConcurrentHashMap<>();
this.httpSessionIdToWsSessions.putIfAbsent(httpSessionId, sessions);
sessions = this.httpSessionIdToWsSessions.get(httpSessionId);
}
sessions.put(wsSession.getId(), wsSession);
}
private void closeWsSessions(String httpSessionId) {
Map<String, WebSocketSession> sessionsToClose = this.httpSessionIdToWsSessions
.remove(httpSessionId);
if (sessionsToClose == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug(
"Closing WebSocket connections associated to expired HTTP Session "
+ httpSessionId);
}
for (WebSocketSession toClose : sessionsToClose.values()) {
try {
toClose.close(SESSION_EXPIRED_STATUS);
}
catch (IOException ex) {
logger.debug(
"Failed to close WebSocketSession (this is nothing to worry about but for debugging only)",
ex);
}
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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.web.socket.server;
import java.time.Instant;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.util.Assert;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* <p>
* Acts as a {@link ChannelInterceptor} and a {@link HandshakeInterceptor} to ensure the
* {@link Session#getLastAccessedTime()} is up to date.
* </p>
* <ul>
* <li>Associates the {@link Session#getId()} with the WebSocket Session attributes when
* the handshake is performed. This is later used when intercepting messages to ensure the
* {@link Session#getLastAccessedTime()} is updated.</li>
* <li>Intercepts {@link Message}'s that are have {@link SimpMessageType} that corresponds
* to {@link #setMatchingMessageTypes(Set)} and updates the last accessed time of the
* {@link Session}. If the {@link Session} is expired, the {@link Message} is prevented
* from proceeding.</li>
* </ul>
*
* <p>
* In order to work {@link SessionRepositoryMessageInterceptor} must be registered as a
* {@link ChannelInterceptor} and a {@link HandshakeInterceptor} .
* </p>
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @since 1.0
*/
public final class SessionRepositoryMessageInterceptor<S extends Session>
implements ChannelInterceptor, HandshakeInterceptor {
private static final String SPRING_SESSION_ID_ATTR_NAME = "SPRING.SESSION.ID";
private final SessionRepository<S> sessionRepository;
private Set<SimpMessageType> matchingMessageTypes;
/**
* Creates a new instance.
*
* @param sessionRepository the {@link SessionRepository} to use. Cannot be null.
*/
public SessionRepositoryMessageInterceptor(SessionRepository<S> sessionRepository) {
Assert.notNull(sessionRepository, "sessionRepository cannot be null");
this.sessionRepository = sessionRepository;
this.matchingMessageTypes = EnumSet.of(SimpMessageType.CONNECT,
SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE,
SimpMessageType.UNSUBSCRIBE);
}
/**
* <p>
* Sets the {@link SimpMessageType} to match on. If the {@link Message} matches, then
* {@link #preSend(Message, MessageChannel)} ensures the {@link Session} is not
* expired and updates the {@link Session#getLastAccessedTime()}
* </p>
*
* <p>
* The default is: SimpMessageType.CONNECT, SimpMessageType.MESSAGE,
* SimpMessageType.SUBSCRIBE, SimpMessageType.UNSUBSCRIBE.
* </p>
*
* @param matchingMessageTypes the {@link SimpMessageType} to match on in
* {@link #preSend(Message, MessageChannel)}, else the {@link Message} is continued
* without accessing or updating the {@link Session}
*/
public void setMatchingMessageTypes(Set<SimpMessageType> matchingMessageTypes) {
Assert.notEmpty(matchingMessageTypes,
"matchingMessageTypes cannot be null or empty");
this.matchingMessageTypes = matchingMessageTypes;
}
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
if (message == null) {
return message;
}
SimpMessageType messageType = SimpMessageHeaderAccessor
.getMessageType(message.getHeaders());
if (!this.matchingMessageTypes.contains(messageType)) {
return message;
}
Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor
.getSessionAttributes(message.getHeaders());
String sessionId = (sessionHeaders != null)
? (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME)
: null;
if (sessionId != null) {
S session = this.sessionRepository.findById(sessionId);
if (session != null) {
// update the last accessed time
session.setLastAccessedTime(Instant.now());
this.sessionRepository.save(session);
}
}
return message;
}
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session != null) {
setSessionId(attributes, session.getId());
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
}
public static String getSessionId(Map<String, Object> attributes) {
return (String) attributes.get(SPRING_SESSION_ID_ATTR_NAME);
}
public static void setSessionId(Map<String, Object> attributes, String sessionId) {
attributes.put(SPRING_SESSION_ID_ATTR_NAME, sessionId);
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2014-2019 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;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MapSessionRepository}.
*/
public class MapSessionRepositoryTests {
private MapSessionRepository repository;
private MapSession session;
@BeforeEach
public void setup() {
this.repository = new MapSessionRepository(new ConcurrentHashMap<>());
this.session = new MapSession();
}
@Test
public void getSessionExpired() {
this.session.setMaxInactiveInterval(Duration.ofSeconds(1));
this.session.setLastAccessedTime(Instant.now().minus(5, ChronoUnit.MINUTES));
this.repository.save(this.session);
assertThat(this.repository.findById(this.session.getId())).isNull();
}
@Test
public void createSessionDefaultExpiration() {
Session session = this.repository.createSession();
assertThat(session).isInstanceOf(MapSession.class);
assertThat(session.getMaxInactiveInterval())
.isEqualTo(new MapSession().getMaxInactiveInterval());
}
@Test
public void createSessionCustomDefaultExpiration() {
final Duration expectedMaxInterval = new MapSession().getMaxInactiveInterval()
.plusSeconds(10);
this.repository.setDefaultMaxInactiveInterval(
(int) expectedMaxInterval.getSeconds());
Session session = this.repository.createSession();
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expectedMaxInterval);
}
@Test
public void changeSessionIdWhenNotYetSaved() {
MapSession createSession = this.repository.createSession();
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession);
assertThat(this.repository.findById(originalId)).isNull();
assertThat(this.repository.findById(createSession.getId())).isNotNull();
}
@Test
public void changeSessionIdWhenSaved() {
MapSession createSession = this.repository.createSession();
this.repository.save(createSession);
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession);
assertThat(this.repository.findById(originalId)).isNull();
assertThat(this.repository.findById(createSession.getId())).isNotNull();
}
@Test // gh-1120
public void getAttributeNamesAndRemove() {
MapSession session = this.repository.createSession();
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
for (String attributeName : session.getAttributeNames()) {
session.removeAttribute(attributeName);
}
assertThat(session.getAttributeNames()).isEmpty();
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2014-2019 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;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
public class MapSessionTests {
private MapSession session;
@BeforeEach
public void setup() {
this.session = new MapSession();
this.session.setLastAccessedTime(Instant.ofEpochMilli(1413258262962L));
}
@Test
public void constructorNullSession() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new MapSession((Session) null))
.withMessage("session cannot be null");
}
@Test
public void getAttributeWhenNullThenNull() {
String result = this.session.getAttribute("attrName");
assertThat(result).isNull();
}
@Test
public void getAttributeOrDefaultWhenNullThenDefaultValue() {
String defaultValue = "default";
String result = this.session.getAttributeOrDefault("attrName", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getAttributeOrDefaultWhenNotNullThenDefaultValue() {
String defaultValue = "default";
String attrValue = "value";
String attrName = "attrName";
this.session.setAttribute(attrName, attrValue);
String result = this.session.getAttributeOrDefault(attrName, defaultValue);
assertThat(result).isEqualTo(attrValue);
}
@Test
public void getRequiredAttributeWhenNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.session.getRequiredAttribute("attrName"))
.withMessage("Required attribute 'attrName' is missing.");
}
@Test
public void getRequiredAttributeWhenNotNullThenReturns() {
String attrValue = "value";
String attrName = "attrName";
this.session.setAttribute(attrName, attrValue);
String result = this.session.getRequiredAttribute("attrName");
assertThat(result).isEqualTo(attrValue);
}
/**
* Ensure conforms to the javadoc of {@link Session}
*/
@Test
public void setAttributeNullObjectRemoves() {
String attr = "attr";
this.session.setAttribute(attr, new Object());
this.session.setAttribute(attr, null);
assertThat(this.session.getAttributeNames()).isEmpty();
}
@Test
public void equalsNonSessionFalse() {
assertThat(this.session.equals(new Object())).isFalse();
}
@Test
public void equalsCustomSession() {
CustomSession other = new CustomSession();
this.session.setId(other.getId());
assertThat(this.session.equals(other)).isTrue();
}
@Test
public void hashCodeEqualsIdHashCode() {
this.session.setId("constantId");
assertThat(this.session.hashCode()).isEqualTo(this.session.getId().hashCode());
}
@Test
public void isExpiredExact() {
Instant now = Instant.ofEpochMilli(1413260062962L);
assertThat(this.session.isExpired(now)).isTrue();
}
@Test
public void isExpiredOneMsTooSoon() {
Instant now = Instant.ofEpochMilli(1413260062961L);
assertThat(this.session.isExpired(now)).isFalse();
}
@Test
public void isExpiredOneMsAfter() {
Instant now = Instant.ofEpochMilli(1413260062963L);
assertThat(this.session.isExpired(now)).isTrue();
}
@Test // gh-1120
public void getAttributeNamesAndRemove() {
this.session.setAttribute("attribute1", "value1");
this.session.setAttribute("attribute2", "value2");
for (String attributeName : this.session.getAttributeNames()) {
this.session.removeAttribute(attributeName);
}
assertThat(this.session.getAttributeNames()).isEmpty();
}
static class CustomSession implements Session {
@Override
public Instant getCreationTime() {
return Instant.EPOCH;
}
@Override
public String changeSessionId() {
throw new UnsupportedOperationException();
}
@Override
public String getId() {
return "id";
}
@Override
public void setLastAccessedTime(Instant lastAccessedTime) {
throw new UnsupportedOperationException();
}
@Override
public Instant getLastAccessedTime() {
return Instant.EPOCH;
}
@Override
public void setMaxInactiveInterval(Duration interval) {
}
@Override
public Duration getMaxInactiveInterval() {
return Duration.ZERO;
}
@Override
public <T> T getAttribute(String attributeName) {
return null;
}
@Override
public Set<String> getAttributeNames() {
return null;
}
@Override
public void setAttribute(String attributeName, Object attributeValue) {
}
@Override
public void removeAttribute(String attributeName) {
}
@Override
public boolean isExpired() {
return false;
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2014-2019 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;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ReactiveMapSessionRepository}.
*
* @author Rob Winch
* @since 2.0
*/
public class ReactiveMapSessionRepositoryTests {
private ReactiveMapSessionRepository repository;
private MapSession session;
@BeforeEach
public void setup() {
this.repository = new ReactiveMapSessionRepository(new HashMap<>());
this.session = new MapSession("session-id");
}
@Test
public void constructorMapThenFound() {
Map<String, Session> sessions = new HashMap<>();
sessions.put(this.session.getId(), this.session);
this.repository = new ReactiveMapSessionRepository(sessions);
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test
public void constructorMapWhenNullThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ReactiveMapSessionRepository(null))
.withMessage("sessions cannot be null");
}
@Test
public void saveWhenNoSubscribersThenNotFound() {
this.repository.save(this.session);
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
}
@Test
public void saveWhenSubscriberThenFound() {
this.repository.save(this.session).block();
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test
public void findByIdWhenExpiredRemovesFromSessionMap() {
this.session.setMaxInactiveInterval(Duration.ofMinutes(1));
this.session.setLastAccessedTime(Instant.now().minus(5, ChronoUnit.MINUTES));
Map<String, Session> sessions = new ConcurrentHashMap<>();
sessions.put("session-id", this.session);
this.repository = new ReactiveMapSessionRepository(sessions);
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
assertThat(sessions).isEmpty();
}
@Test
public void createSessionWhenDefaultMaxInactiveIntervalThenDefaultMaxInactiveInterval() {
Session session = this.repository.createSession().block();
assertThat(session).isInstanceOf(MapSession.class);
assertThat(session.getMaxInactiveInterval())
.isEqualTo(new MapSession().getMaxInactiveInterval());
}
@Test
public void createSessionWhenCustomMaxInactiveIntervalThenCustomMaxInactiveInterval() {
final Duration expectedMaxInterval = new MapSession().getMaxInactiveInterval()
.plusSeconds(10);
this.repository
.setDefaultMaxInactiveInterval((int) expectedMaxInterval.getSeconds());
Session session = this.repository.createSession().block();
assertThat(session.getMaxInactiveInterval()).isEqualTo(expectedMaxInterval);
}
@Test
public void changeSessionIdWhenNotYetSaved() {
MapSession createSession = this.repository.createSession().block();
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession).block();
assertThat(this.repository.findById(originalId).block()).isNull();
assertThat(this.repository.findById(createSession.getId()).block()).isNotNull();
}
@Test
public void changeSessionIdWhenSaved() {
MapSession createSession = this.repository.createSession().block();
this.repository.save(createSession).block();
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession).block();
assertThat(this.repository.findById(originalId).block()).isNull();
assertThat(this.repository.findById(createSession.getId()).block()).isNotNull();
}
@Test // gh-1120
public void getAttributeNamesAndRemove() {
MapSession session = this.repository.createSession().block();
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
for (String attributeName : session.getAttributeNames()) {
session.removeAttribute(attributeName);
}
assertThat(session.getAttributeNames()).isEmpty();
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2014-2019 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.config.annotation.web.http;
import java.io.IOException;
import java.util.Collections;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringHttpSessionConfiguration} using a custom
* {@link CookieSerializer}.
*
* @author Rob Winch
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
public class EnableSpringHttpSessionCustomCookieSerializerTests {
@Autowired
private MockHttpServletRequest request;
@Autowired
private MockHttpServletResponse response;
private MockFilterChain chain;
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
@Autowired
private SessionRepository sessionRepository;
@Autowired
private CookieSerializer cookieSerializer;
@BeforeEach
public void setup() {
this.chain = new MockFilterChain();
reset(this.sessionRepository);
reset(this.cookieSerializer);
}
@Test
public void usesReadSessionIds() throws Exception {
String sessionId = "sessionId";
given(this.cookieSerializer.readCookieValues(any(HttpServletRequest.class)))
.willReturn(Collections.singletonList(sessionId));
given(this.sessionRepository.findById(anyString()))
.willReturn(new MapSession(sessionId));
this.sessionRepositoryFilter.doFilter(this.request, this.response, this.chain);
assertThat(getRequest().getRequestedSessionId()).isEqualTo(sessionId);
}
@Test
public void usesWrite() throws Exception {
given(this.sessionRepository.createSession()).willReturn(new MapSession());
this.sessionRepositoryFilter.doFilter(this.request, this.response,
new MockFilterChain() {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
((HttpServletRequest) request).getSession();
super.doFilter(request, response);
}
});
verify(this.cookieSerializer).writeCookieValue(any(CookieValue.class));
}
private HttpServletRequest getRequest() {
return (HttpServletRequest) this.chain.getRequest();
}
@EnableSpringHttpSession
@Configuration
static class Config {
@Bean
public SessionRepository sessionRepository() {
return mock(SessionRepository.class);
}
@Bean
public CookieSerializer cookieSerializer() {
return mock(CookieSerializer.class);
}
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2014-2019 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.config.annotation.web.http;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
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;
/**
* Tests for {@link SpringHttpSessionConfiguration}.
*
* @author Vedran Pavic
*/
public class SpringHttpSessionConfigurationTests {
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@AfterEach
public void closeContext() {
if (this.context != null) {
this.context.close();
}
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
}
@Test
public void noSessionRepositoryConfiguration() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> registerAndRefresh(EmptyConfiguration.class))
.withMessageContaining("org.springframework.session.SessionRepository");
}
@Test
public void defaultConfiguration() {
registerAndRefresh(DefaultConfiguration.class);
assertThat(this.context.getBean(SessionEventHttpSessionListenerAdapter.class))
.isNotNull();
assertThat(this.context.getBean(SessionRepositoryFilter.class)).isNotNull();
assertThat(this.context.getBean(SessionRepository.class)).isNotNull();
}
@Test
public void sessionCookieConfigConfiguration() {
registerAndRefresh(SessionCookieConfigConfiguration.class);
SessionRepositoryFilter sessionRepositoryFilter = this.context
.getBean(SessionRepositoryFilter.class);
assertThat(sessionRepositoryFilter).isNotNull();
CookieHttpSessionIdResolver httpSessionIdResolver = (CookieHttpSessionIdResolver) ReflectionTestUtils
.getField(sessionRepositoryFilter, "httpSessionIdResolver");
assertThat(httpSessionIdResolver).isNotNull();
DefaultCookieSerializer cookieSerializer = (DefaultCookieSerializer) ReflectionTestUtils
.getField(httpSessionIdResolver, "cookieSerializer");
assertThat(cookieSerializer).isNotNull();
assertThat(ReflectionTestUtils.getField(cookieSerializer, "cookieName"))
.isEqualTo("test-name");
assertThat(ReflectionTestUtils.getField(cookieSerializer, "cookiePath"))
.isEqualTo("test-path");
assertThat(ReflectionTestUtils.getField(cookieSerializer, "cookieMaxAge"))
.isEqualTo(600);
assertThat(ReflectionTestUtils.getField(cookieSerializer, "domainName"))
.isEqualTo("test-domain");
}
@Test
public void rememberMeServicesConfiguration() {
registerAndRefresh(RememberMeServicesConfiguration.class);
SessionRepositoryFilter sessionRepositoryFilter = this.context
.getBean(SessionRepositoryFilter.class);
assertThat(sessionRepositoryFilter).isNotNull();
CookieHttpSessionIdResolver httpSessionIdResolver = (CookieHttpSessionIdResolver) ReflectionTestUtils
.getField(sessionRepositoryFilter, "httpSessionIdResolver");
assertThat(httpSessionIdResolver).isNotNull();
DefaultCookieSerializer cookieSerializer = (DefaultCookieSerializer) ReflectionTestUtils
.getField(httpSessionIdResolver, "cookieSerializer");
assertThat(cookieSerializer).isNotNull();
assertThat(ReflectionTestUtils.getField(cookieSerializer,
"rememberMeRequestAttribute")).isEqualTo(
SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR);
}
@Configuration
@EnableSpringHttpSession
static class EmptyConfiguration {
}
static class BaseConfiguration {
@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
}
@Configuration
@EnableSpringHttpSession
static class DefaultConfiguration extends BaseConfiguration {
}
@Configuration
@EnableSpringHttpSession
static class SessionCookieConfigConfiguration extends BaseConfiguration {
@Bean
public ServletContext servletContext() {
MockServletContext servletContext = new MockServletContext();
servletContext.getSessionCookieConfig().setName("test-name");
servletContext.getSessionCookieConfig().setDomain("test-domain");
servletContext.getSessionCookieConfig().setPath("test-path");
servletContext.getSessionCookieConfig().setMaxAge(600);
return servletContext;
}
}
@Configuration
@EnableSpringHttpSession
static class RememberMeServicesConfiguration extends BaseConfiguration {
@Bean
public SpringSessionRememberMeServices rememberMeServices() {
return new SpringSessionRememberMeServices();
}
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2014-2019 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.config.annotation.web.server;
import java.util.HashMap;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.session.ReactiveMapSessionRepository;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.session.CookieWebSessionIdResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.HeaderWebSessionIdResolver;
import org.springframework.web.server.session.WebSessionIdResolver;
import org.springframework.web.server.session.WebSessionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Verify various configurations through {@link EnableSpringWebSession}.
*
* @author Greg Turnquist
*/
public class SpringWebSessionConfigurationTests {
private AnnotationConfigApplicationContext context;
@AfterEach
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void enableSpringWebSessionConfiguresThings() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(GoodConfig.class);
this.context.refresh();
WebSessionManager webSessionManagerFoundByType = this.context.getBean(WebSessionManager.class);
Object webSessionManagerFoundByName = this.context.getBean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME);
assertThat(webSessionManagerFoundByType).isNotNull();
assertThat(webSessionManagerFoundByName).isNotNull();
assertThat(webSessionManagerFoundByType).isEqualTo(webSessionManagerFoundByName);
assertThat(this.context.getBean(ReactiveSessionRepository.class)).isNotNull();
}
@Test
public void missingReactiveSessionRepositoryBreaksAppContext() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(BadConfig.class);
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(this.context::refresh)
.withMessageContaining("Error creating bean with name 'webSessionManager'")
.withMessageContaining("No qualifying bean of type '" + ReactiveSessionRepository.class.getCanonicalName());
}
@Test
public void defaultSessionIdResolverShouldBeCookieBased() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(GoodConfig.class);
this.context.refresh();
DefaultWebSessionManager manager = this.context.getBean(DefaultWebSessionManager.class);
assertThat(manager.getSessionIdResolver().getClass()).isAssignableFrom(CookieWebSessionIdResolver.class);
}
@Test
public void providedSessionIdResolverShouldBePickedUpAutomatically() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(OverrideSessionIdResolver.class);
this.context.refresh();
DefaultWebSessionManager manager = this.context.getBean(DefaultWebSessionManager.class);
assertThat(manager.getSessionIdResolver().getClass()).isAssignableFrom(HeaderWebSessionIdResolver.class);
}
/**
* A configuration with all the right parts.
*/
@EnableSpringWebSession
static class GoodConfig {
/**
* Use Reactor-friendly, {@link java.util.Map}-backed {@link ReactiveSessionRepository} for test purposes.
*/
@Bean
ReactiveSessionRepository<?> reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new HashMap<>());
}
}
/**
* A configuration where no {@link ReactiveSessionRepository} is defined. It's BAD!
*/
@EnableSpringWebSession
static class BadConfig {
}
@EnableSpringWebSession
static class OverrideSessionIdResolver {
@Bean
ReactiveSessionRepository<?> reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new HashMap<>());
}
@Bean
WebSessionIdResolver alternateWebSessionIdResolver() {
return new HeaderWebSessionIdResolver();
}
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2014-2019 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.security;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.User;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.verify;
import static org.mockito.BDDMockito.when;
/**
* Tests for {@link SpringSessionBackedSessionRegistry}.
*/
public class SpringSessionBackedSessionRegistryTest {
private static final String SESSION_ID = "sessionId";
private static final String SESSION_ID2 = "otherSessionId";
private static final String USER_NAME = "userName";
private static final User PRINCIPAL = new User(USER_NAME, "password",
Collections.emptyList());
private static final Instant NOW = Instant.now();
@Mock
private FindByIndexNameSessionRepository<Session> sessionRepository;
@InjectMocks
private SpringSessionBackedSessionRegistry<Session> sessionRegistry;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void sessionInformationForExistingSession() {
Session session = createSession(SESSION_ID, USER_NAME, NOW);
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
SessionInformation sessionInfo = this.sessionRegistry
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isFalse();
}
@Test
public void sessionInformationForExpiredSession() {
Session session = createSession(SESSION_ID, USER_NAME, NOW);
session.setAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR,
Boolean.TRUE);
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
SessionInformation sessionInfo = this.sessionRegistry
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isTrue();
}
@Test
public void noSessionInformationForMissingSession() {
assertThat(this.sessionRegistry.getSessionInformation("nonExistingSessionId"))
.isNull();
}
@Test
public void getAllSessions() {
setUpSessions();
List<SessionInformation> allSessionInfos = this.sessionRegistry
.getAllSessions(PRINCIPAL, true);
assertThat(allSessionInfos).extracting("sessionId").containsExactly(SESSION_ID,
SESSION_ID2);
}
@Test
public void getNonExpiredSessions() {
setUpSessions();
List<SessionInformation> nonExpiredSessionInfos = this.sessionRegistry
.getAllSessions(PRINCIPAL, false);
assertThat(nonExpiredSessionInfos).extracting("sessionId")
.containsExactly(SESSION_ID2);
}
@Test
public void expireNow() {
Session session = createSession(SESSION_ID, USER_NAME, NOW);
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
SessionInformation sessionInfo = this.sessionRegistry
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.isExpired()).isFalse();
sessionInfo.expireNow();
assertThat(sessionInfo.isExpired()).isTrue();
ArgumentCaptor<Session> captor = ArgumentCaptor.forClass(Session.class);
verify(this.sessionRepository).save(captor.capture());
assertThat(captor.getValue().<Boolean>getAttribute(
SpringSessionBackedSessionInformation.EXPIRED_ATTR))
.isEqualTo(Boolean.TRUE);
}
private Session createSession(String sessionId, String userName,
Instant lastAccessed) {
MapSession session = new MapSession(sessionId);
session.setLastAccessedTime(lastAccessed);
Authentication authentication = mock(Authentication.class);
when(authentication.getName()).thenReturn(userName);
SecurityContextImpl securityContext = new SecurityContextImpl();
securityContext.setAuthentication(authentication);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
return session;
}
private void setUpSessions() {
Session session1 = createSession(SESSION_ID, USER_NAME, NOW);
session1.setAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR,
Boolean.TRUE);
Session session2 = createSession(SESSION_ID2, USER_NAME, NOW);
Map<String, Session> sessions = new LinkedHashMap<>();
sessions.put(session1.getId(), session1);
sessions.put(session2.getId(), session2);
when(this.sessionRepository.findByPrincipalName(USER_NAME))
.thenReturn(sessions);
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2014-2019 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.security.web.authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link SpringSessionRememberMeServices}.
*
* @author Vedran Pavic
*/
public class SpringSessionRememberMeServicesTests {
private SpringSessionRememberMeServices rememberMeServices;
@Test
public void create() {
this.rememberMeServices = new SpringSessionRememberMeServices();
assertThat(ReflectionTestUtils.getField(this.rememberMeServices,
"rememberMeParameterName")).isEqualTo("remember-me");
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
.isEqualTo(false);
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
.isEqualTo(2592000);
}
@Test
public void createWithCustomParameter() {
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setRememberMeParameterName("test-param");
assertThat(ReflectionTestUtils.getField(this.rememberMeServices,
"rememberMeParameterName")).isEqualTo("test-param");
}
@Test
public void createWithNullParameter() {
this.rememberMeServices = new SpringSessionRememberMeServices();
assertThatIllegalArgumentException()
.isThrownBy(
() -> this.rememberMeServices.setRememberMeParameterName(null))
.withMessage("rememberMeParameterName cannot be empty or null");
}
@Test
public void createWithAlwaysRemember() {
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setAlwaysRemember(true);
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
.isEqualTo(true);
}
@Test
public void createWithCustomValidity() {
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setValiditySeconds(100000);
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
.isEqualTo(100000);
}
@Test
public void autoLogin() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.autoLogin(request, response);
verifyZeroInteractions(request, response);
}
// gh-752
@Test
public void loginFailRemoveSecurityContext() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
HttpSession session = mock(HttpSession.class);
given(request.getSession(eq(false))).willReturn(session);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.loginFail(request, response);
verify(request, times(1)).getSession(eq(false));
verify(session, times(1)).removeAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
verifyZeroInteractions(request, response, session);
}
@Test
public void loginSuccess() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
Authentication authentication = mock(Authentication.class);
HttpSession session = mock(HttpSession.class);
given(request.getParameter(eq("remember-me"))).willReturn("true");
given(request.getSession()).willReturn(session);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.loginSuccess(request, response, authentication);
verify(request, times(1)).getParameter(eq("remember-me"));
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
verifyZeroInteractions(request, response, session, authentication);
}
@Test
public void loginSuccessWithCustomParameter() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
Authentication authentication = mock(Authentication.class);
HttpSession session = mock(HttpSession.class);
given(request.getParameter(eq("test-param"))).willReturn("true");
given(request.getSession()).willReturn(session);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setRememberMeParameterName("test-param");
this.rememberMeServices.loginSuccess(request, response, authentication);
verify(request, times(1)).getParameter(eq("test-param"));
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
verifyZeroInteractions(request, response, session, authentication);
}
@Test
public void loginSuccessWithAlwaysRemember() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
Authentication authentication = mock(Authentication.class);
HttpSession session = mock(HttpSession.class);
given(request.getSession()).willReturn(session);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setAlwaysRemember(true);
this.rememberMeServices.loginSuccess(request, response, authentication);
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
verifyZeroInteractions(request, response, session, authentication);
}
@Test
public void loginSuccessWithCustomValidity() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
Authentication authentication = mock(Authentication.class);
HttpSession session = mock(HttpSession.class);
given(request.getParameter(eq("remember-me"))).willReturn("true");
given(request.getSession()).willReturn(session);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setValiditySeconds(100000);
this.rememberMeServices.loginSuccess(request, response, authentication);
verify(request, times(1)).getParameter(eq("remember-me"));
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(100000));
verifyZeroInteractions(request, response, session, authentication);
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2014-2019 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.web.http;
import java.util.Base64;
import java.util.Collections;
import javax.servlet.http.Cookie;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CookieHttpSessionIdResolver}.
*/
public class CookieHttpSessionIdResolverTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private CookieHttpSessionIdResolver strategy;
private String cookieName;
private Session session;
@BeforeEach
public void setup() throws Exception {
this.cookieName = "SESSION";
this.session = new MapSession();
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.strategy = new CookieHttpSessionIdResolver();
}
@Test
public void getRequestedSessionIdNull() throws Exception {
assertThat(this.strategy.resolveSessionIds(this.request)).isEmpty();
}
@Test
public void getRequestedSessionIdNotNull() throws Exception {
setSessionCookie(this.session.getId());
assertThat(this.strategy.resolveSessionIds(this.request))
.isEqualTo(Collections.singletonList(this.session.getId()));
}
@Test
public void getRequestedSessionIdNotNullCustomCookieName() throws Exception {
setCookieName("CUSTOM");
setSessionCookie(this.session.getId());
assertThat(this.strategy.resolveSessionIds(this.request))
.isEqualTo(Collections.singletonList(this.session.getId()));
}
@Test
public void onNewSession() throws Exception {
this.strategy.setSessionId(this.request, this.response, this.session.getId());
assertThat(getSessionId()).isEqualTo(this.session.getId());
}
@Test
public void onNewSessionTwiceSameId() throws Exception {
this.strategy.setSessionId(this.request, this.response, this.session.getId());
this.strategy.setSessionId(this.request, this.response, this.session.getId());
assertThat(this.response.getCookies()).hasSize(1);
}
@Test
public void onNewSessionTwiceNewId() throws Exception {
Session newSession = new MapSession();
this.strategy.setSessionId(this.request, this.response, this.session.getId());
this.strategy.setSessionId(this.request, this.response, newSession.getId());
Cookie[] cookies = this.response.getCookies();
assertThat(cookies).hasSize(2);
assertThat(base64Decode(cookies[0].getValue())).isEqualTo(this.session.getId());
assertThat(base64Decode(cookies[1].getValue())).isEqualTo(newSession.getId());
}
@Test
public void onNewSessionCookiePath() throws Exception {
this.request.setContextPath("/somethingunique");
this.strategy.setSessionId(this.request, this.response, this.session.getId());
Cookie sessionCookie = this.response.getCookie(this.cookieName);
assertThat(sessionCookie.getPath())
.isEqualTo(this.request.getContextPath() + "/");
}
@Test
public void onNewSessionCustomCookieName() throws Exception {
setCookieName("CUSTOM");
this.strategy.setSessionId(this.request, this.response, this.session.getId());
assertThat(getSessionId()).isEqualTo(this.session.getId());
}
@Test
public void onDeleteSession() throws Exception {
this.strategy.expireSession(this.request, this.response);
assertThat(getSessionId()).isEmpty();
}
@Test
public void onDeleteSessionCookiePath() throws Exception {
this.request.setContextPath("/somethingunique");
this.strategy.expireSession(this.request, this.response);
Cookie sessionCookie = this.response.getCookie(this.cookieName);
assertThat(sessionCookie.getPath())
.isEqualTo(this.request.getContextPath() + "/");
}
@Test
public void onDeleteSessionCustomCookieName() throws Exception {
setCookieName("CUSTOM");
this.strategy.expireSession(this.request, this.response);
assertThat(getSessionId()).isEmpty();
}
@Test
public void createSessionCookieValue() {
assertThat(createSessionCookieValue(17)).isEqualToIgnoringCase(
"0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 a 10 b 11 c 12 d 13 e 14 f 15 10 16");
}
private String createSessionCookieValue(long size) {
StringBuilder sb = new StringBuilder();
for (long i = 0; i < size; i++) {
String hex = Long.toHexString(i);
sb.append(hex);
sb.append(" ");
sb.append(i);
if (i < size - 1) {
sb.append(" ");
}
}
return sb.toString();
}
private void setCookieName(String cookieName) {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName(cookieName);
this.strategy.setCookieSerializer(cookieSerializer);
this.cookieName = cookieName;
}
private void setSessionCookie(String value) {
this.request.setCookies(new Cookie(this.cookieName, base64Encode(value)));
}
private String getSessionId() {
return base64Decode(this.response.getCookie(this.cookieName).getValue());
}
private static String base64Encode(String value) {
return Base64.getEncoder().encodeToString(value.getBytes());
}
private static String base64Decode(String value) {
return new String(Base64.getDecoder().decode(value));
}
}

View File

@@ -0,0 +1,552 @@
/*
* Copyright 2014-2019 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.web.http;
import java.util.Base64;
import javax.servlet.http.Cookie;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
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.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link DefaultCookieSerializer}.
*
* @author Rob Winch
* @author Vedran Pavic
* @author Eddú Meléndez
*/
public class DefaultCookieSerializerTests {
private String cookieName;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private DefaultCookieSerializer serializer;
private String sessionId;
@BeforeEach
public void setup() {
this.cookieName = "SESSION";
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.sessionId = "sessionId";
this.serializer = new DefaultCookieSerializer();
}
// --- readCookieValues ---
@Test
public void readCookieValuesNull() {
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieValuesSingle(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
this.request.setCookies(
createCookie(this.cookieName, this.sessionId, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request))
.containsOnly(this.sessionId);
}
@Test
public void readCookieSerializerUseBase64EncodingTrueValuesNotBase64() {
this.sessionId = "&^%$*";
this.serializer.setUseBase64Encoding(true);
this.request.setCookies(new Cookie(this.cookieName, this.sessionId));
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieValuesSingleAndInvalidName(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
this.request.setCookies(
createCookie(this.cookieName, this.sessionId, useBase64Encoding),
createCookie(this.cookieName + "INVALID", this.sessionId + "INVALID",
useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request))
.containsOnly(this.sessionId);
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieValuesMulti(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
String secondSession = "secondSessionId";
this.request.setCookies(
createCookie(this.cookieName, this.sessionId, useBase64Encoding),
createCookie(this.cookieName, secondSession, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request))
.containsExactly(this.sessionId, secondSession);
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieValuesMultiCustomSessionCookieName(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
setCookieName("JSESSIONID");
String secondSession = "secondSessionId";
this.request.setCookies(
createCookie(this.cookieName, this.sessionId, useBase64Encoding),
createCookie(this.cookieName, secondSession, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request))
.containsExactly(this.sessionId, secondSession);
}
// gh-392
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieValuesNullCookieValue(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieValuesNullCookieValueAndJvmRoute(boolean useBase64Encoding) {
this.serializer.setJvmRoute("123");
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieValuesNullCookieValueAndNotNullCookie(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
this.serializer.setJvmRoute("123");
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding),
createCookie(this.cookieName, this.sessionId, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request))
.containsOnly(this.sessionId);
}
// --- writeCookie ---
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void writeCookie(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookieValue(useBase64Encoding)).isEqualTo(this.sessionId);
}
// --- httpOnly ---
@Test
public void writeCookieHttpOnlyDefault() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isHttpOnly()).isTrue();
}
@Test
public void writeCookieHttpOnlySetTrue() {
this.serializer.setUseHttpOnlyCookie(true);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isHttpOnly()).isTrue();
}
@Test
public void writeCookieHttpOnlySetFalse() {
this.serializer.setUseHttpOnlyCookie(false);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isHttpOnly()).isFalse();
}
// --- domainName ---
@Test
public void writeCookieDomainNameDefault() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getDomain()).isNull();
}
@Test
public void writeCookieDomainNameCustom() {
String domainName = "example.com";
this.serializer.setDomainName(domainName);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getDomain()).isEqualTo(domainName);
}
@Test
public void setDomainNameAndDomainNamePatternThrows() {
this.serializer.setDomainName("example.com");
assertThatIllegalStateException()
.isThrownBy(() -> this.serializer.setDomainNamePattern(".*"))
.withMessage("Cannot set both domainName and domainNamePattern");
}
// --- domainNamePattern ---
@Test
public void writeCookieDomainNamePattern() {
String domainNamePattern = "^.+?\\.(\\w+\\.[a-z]+)$";
this.serializer.setDomainNamePattern(domainNamePattern);
String[] matchingDomains = { "child.sub.example.com", "www.example.com" };
for (String domain : matchingDomains) {
this.request.setServerName(domain);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getDomain()).isEqualTo("example.com");
this.response = new MockHttpServletResponse();
}
String[] notMatchingDomains = { "example.com", "localhost", "127.0.0.1" };
for (String domain : notMatchingDomains) {
this.request.setServerName(domain);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getDomain()).isNull();
this.response = new MockHttpServletResponse();
}
}
@Test
public void setDomainNamePatternAndDomainNameThrows() {
this.serializer.setDomainNamePattern(".*");
assertThatIllegalStateException()
.isThrownBy(() -> this.serializer.setDomainName("example.com"))
.withMessage("Cannot set both domainName and domainNamePattern");
}
// --- cookieName ---
@Test
public void writeCookieCookieNameDefault() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getName()).isEqualTo("SESSION");
}
@Test
public void writeCookieCookieNameCustom() {
String cookieName = "JSESSIONID";
setCookieName(cookieName);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getName()).isEqualTo(cookieName);
}
@Test
public void setCookieNameNullThrows() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.serializer.setCookieName(null))
.withMessage("cookieName cannot be null");
}
// --- cookiePath ---
@Test
public void writeCookieCookiePathDefaultEmptyContextPathUsed() {
this.request.setContextPath("");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getPath()).isEqualTo("/");
}
@Test
public void writeCookieCookiePathDefaultContextPathUsed() {
this.request.setContextPath("/context");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getPath()).isEqualTo("/context/");
}
@Test
public void writeCookieCookiePathExplicitNullCookiePathContextPathUsed() {
this.request.setContextPath("/context");
this.serializer.setCookiePath(null);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getPath()).isEqualTo("/context/");
}
@Test
public void writeCookieCookiePathExplicitCookiePath() {
this.request.setContextPath("/context");
this.serializer.setCookiePath("/");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getPath()).isEqualTo("/");
}
// --- cookieMaxAge ---
@Test
public void writeCookieCookieMaxAgeDefault() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getMaxAge()).isEqualTo(-1);
}
@Test
public void writeCookieCookieMaxAgeExplicit() {
this.serializer.setCookieMaxAge(100);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
@Test
public void writeCookieCookieMaxAgeExplicitEmptyCookie() {
this.serializer.setCookieMaxAge(100);
this.serializer.writeCookieValue(cookieValue(""));
assertThat(getCookie().getMaxAge()).isEqualTo(0);
}
@Test
public void writeCookieCookieMaxAgeExplicitCookieValue() {
CookieValue cookieValue = cookieValue(this.sessionId);
cookieValue.setCookieMaxAge(100);
this.serializer.writeCookieValue(cookieValue);
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
// --- secure ---
@Test
public void writeCookieDefaultInsecureRequest() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSecure()).isFalse();
}
@Test
public void writeCookieSecureSecureRequest() {
this.request.setSecure(true);
this.serializer.setUseSecureCookie(true);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSecure()).isTrue();
}
@Test
public void writeCookieSecureInsecureRequest() {
this.serializer.setUseSecureCookie(true);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSecure()).isTrue();
}
@Test
public void writeCookieInsecureSecureRequest() {
this.request.setSecure(true);
this.serializer.setUseSecureCookie(false);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSecure()).isFalse();
}
@Test
public void writeCookieInecureInsecureRequest() {
this.serializer.setUseSecureCookie(false);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSecure()).isFalse();
}
// --- jvmRoute ---
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void writeCookieJvmRoute(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
String jvmRoute = "route";
this.serializer.setJvmRoute(jvmRoute);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookieValue(useBase64Encoding))
.isEqualTo(this.sessionId + "." + jvmRoute);
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieJvmRoute(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
String jvmRoute = "route";
this.serializer.setJvmRoute(jvmRoute);
this.request.setCookies(createCookie(this.cookieName,
this.sessionId + "." + jvmRoute, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request))
.containsOnly(this.sessionId);
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieJvmRouteRouteMissing(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
String jvmRoute = "route";
this.serializer.setJvmRoute(jvmRoute);
this.request.setCookies(
createCookie(this.cookieName, this.sessionId, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request))
.containsOnly(this.sessionId);
}
@ParameterizedTest
@ValueSource(strings = { "true", "false" })
public void readCookieJvmRouteOnlyRoute(boolean useBase64Encoding) {
this.serializer.setUseBase64Encoding(useBase64Encoding);
String jvmRoute = "route";
this.serializer.setJvmRoute(jvmRoute);
this.request.setCookies(
createCookie(this.cookieName, "." + jvmRoute, useBase64Encoding));
assertThat(this.serializer.readCookieValues(this.request)).containsOnly("");
}
// --- rememberMe ---
@Test
public void writeCookieRememberMeCookieMaxAgeDefault() {
this.request.setAttribute("rememberMe", true);
this.serializer.setRememberMeRequestAttribute("rememberMe");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getMaxAge()).isEqualTo(Integer.MAX_VALUE);
}
@Test
public void writeCookieRememberMeCookieMaxAgeOverride() {
this.request.setAttribute("rememberMe", true);
this.serializer.setRememberMeRequestAttribute("rememberMe");
CookieValue cookieValue = cookieValue(this.sessionId);
cookieValue.setCookieMaxAge(100);
this.serializer.writeCookieValue(cookieValue);
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);
}
private Cookie createCookie(String name, String value, boolean useBase64Encoding) {
if (useBase64Encoding && StringUtils.hasLength(value)) {
value = new String(Base64.getEncoder().encode(value.getBytes()));
}
return new Cookie(name, value);
}
private MockCookie getCookie() {
return (MockCookie) this.response.getCookie(this.cookieName);
}
private String getCookieValue(boolean useBase64Encoding) {
String value = getCookie().getValue();
if (!useBase64Encoding) {
return value;
}
if (value == null) {
return null;
}
return new String(Base64.getDecoder().decode(value));
}
private CookieValue cookieValue(String cookieValue) {
return new CookieValue(this.request, this.response, cookieValue);
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2014-2019 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.web.http;
import java.util.Collections;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
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.assertThatIllegalArgumentException;
/**
* Tests for {@link HeaderHttpSessionIdResolver}.
*/
public class HeaderHttpSessionIdResolverTests {
private static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HeaderHttpSessionIdResolver resolver;
@BeforeEach
public void setup() {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.resolver = HeaderHttpSessionIdResolver.xAuthToken();
}
@Test
public void createResolverWithXAuthTokenHeader() {
HeaderHttpSessionIdResolver resolver = HeaderHttpSessionIdResolver.xAuthToken();
assertThat(ReflectionTestUtils.getField(resolver, "headerName"))
.isEqualTo("X-Auth-Token");
}
@Test
public void createResolverWithAuthenticationInfoHeader() {
HeaderHttpSessionIdResolver resolver = HeaderHttpSessionIdResolver
.authenticationInfo();
assertThat(ReflectionTestUtils.getField(resolver, "headerName"))
.isEqualTo("Authentication-Info");
}
@Test
public void createResolverWithCustomHeaderName() {
HeaderHttpSessionIdResolver resolver = new HeaderHttpSessionIdResolver(
"Custom-Header");
assertThat(ReflectionTestUtils.getField(resolver, "headerName"))
.isEqualTo("Custom-Header");
}
@Test
public void createResolverWithNullHeaderName() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new HeaderHttpSessionIdResolver(null))
.withMessage("headerName cannot be null");
}
@Test
public void getRequestedSessionIdNull() {
assertThat(this.resolver.resolveSessionIds(this.request)).isEmpty();
}
@Test
public void getRequestedSessionIdNotNull() {
String sessionId = UUID.randomUUID().toString();
setSessionId(sessionId);
assertThat(this.resolver.resolveSessionIds(this.request))
.isEqualTo(Collections.singletonList(sessionId));
}
@Test
public void onNewSession() {
String sessionId = UUID.randomUUID().toString();
this.resolver.setSessionId(this.request, this.response, sessionId);
assertThat(getSessionId()).isEqualTo(sessionId);
}
@Test
public void onDeleteSession() {
this.resolver.expireSession(this.request, this.response);
assertThat(getSessionId()).isEmpty();
}
// the header is set as apposed to added
@Test
public void onNewSessionMulti() {
String sessionId = UUID.randomUUID().toString();
this.resolver.setSessionId(this.request, this.response, sessionId);
this.resolver.setSessionId(this.request, this.response, sessionId);
assertThat(this.response.getHeaders(HEADER_X_AUTH_TOKEN).size()).isEqualTo(1);
assertThat(this.response.getHeaders(HEADER_X_AUTH_TOKEN)).containsOnly(sessionId);
}
// the header is set as apposed to added
@Test
public void onDeleteSessionMulti() {
this.resolver.expireSession(this.request, this.response);
this.resolver.expireSession(this.request, this.response);
assertThat(this.response.getHeaders(HEADER_X_AUTH_TOKEN).size()).isEqualTo(1);
assertThat(getSessionId()).isEmpty();
}
private void setSessionId(String sessionId) {
this.request.addHeader(HEADER_X_AUTH_TOKEN, sessionId);
}
private String getSessionId() {
return this.response.getHeader(HEADER_X_AUTH_TOKEN);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2014-2019 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.web.http;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
public class OncePerRequestFilterTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockFilterChain chain;
private OncePerRequestFilter filter;
private HttpServlet servlet;
private List<OncePerRequestFilter> invocations;
@BeforeEach
@SuppressWarnings("serial")
public void setup() {
this.servlet = new HttpServlet() {
};
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
this.invocations = new ArrayList<>();
this.filter = new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
OncePerRequestFilterTests.this.invocations.add(this);
filterChain.doFilter(request, response);
}
};
}
@Test
public void doFilterOnce() throws ServletException, IOException {
this.filter.doFilter(this.request, this.response, this.chain);
assertThat(this.invocations).containsOnly(this.filter);
}
@Test
public void doFilterMultiOnlyIvokesOnce() throws ServletException, IOException {
this.filter.doFilter(this.request, this.response,
new MockFilterChain(this.servlet, this.filter));
assertThat(this.invocations).containsOnly(this.filter);
}
@Test
public void doFilterOtherSubclassInvoked() throws ServletException, IOException {
OncePerRequestFilter filter2 = new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
OncePerRequestFilterTests.this.invocations.add(this);
filterChain.doFilter(request, response);
}
};
this.filter.doFilter(this.request, this.response,
new MockFilterChain(this.servlet, filter2));
assertThat(this.invocations).containsOnly(this.filter, filter2);
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2014-2019 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.web.http;
import java.util.Arrays;
import java.util.Collections;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link SessionEventHttpSessionListenerAdapter}.
*
* @author Rob Winch
* @since 1.1
*/
public class SessionEventHttpSessionListenerAdapterTests {
@Mock
private HttpSessionListener listener1;
@Mock
private HttpSessionListener listener2;
@Mock
private ServletContext servletContext;
@Captor
private ArgumentCaptor<HttpSessionEvent> sessionEvent;
private SessionDestroyedEvent destroyed;
private SessionCreatedEvent created;
private SessionEventHttpSessionListenerAdapter listener;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
this.listener = new SessionEventHttpSessionListenerAdapter(
Arrays.asList(this.listener1, this.listener2));
this.listener.setServletContext(new MockServletContext());
Session session = new MapSession();
this.destroyed = new SessionDestroyedEvent(this, session);
this.created = new SessionCreatedEvent(this, session);
}
// We want relaxed constructor that will allow for an empty listeners to
// make configuration easier (i.e. autowire all HttpSessionListeners and might get
// none)
@Test
public void constructorEmptyWorks() {
new SessionEventHttpSessionListenerAdapter(Collections.emptyList());
}
/**
* Make sure that we short circuit onApplicationEvent as early as possible if no
* listeners
*/
@Test
public void onApplicationEventEmptyListenersDoesNotUseEvent() {
this.listener = new SessionEventHttpSessionListenerAdapter(
Collections.emptyList());
this.destroyed = mock(SessionDestroyedEvent.class);
this.listener.onApplicationEvent(this.destroyed);
verifyZeroInteractions(this.destroyed, this.listener1, this.listener2);
}
@Test
public void onApplicationEventDestroyed() {
this.listener.onApplicationEvent(this.destroyed);
verify(this.listener1).sessionDestroyed(this.sessionEvent.capture());
verify(this.listener2).sessionDestroyed(this.sessionEvent.capture());
assertThat(this.sessionEvent.getValue().getSession().getId())
.isEqualTo(this.destroyed.getSessionId());
}
@Test
public void onApplicationEventCreated() {
this.listener.onApplicationEvent(this.created);
verify(this.listener1).sessionCreated(this.sessionEvent.capture());
verify(this.listener2).sessionCreated(this.sessionEvent.capture());
assertThat(this.sessionEvent.getValue().getSession().getId())
.isEqualTo(this.created.getSessionId());
}
}

View File

@@ -0,0 +1,283 @@
/*
* Copyright 2014-2019 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.web.server.session;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Mono;
import org.springframework.session.ReactiveSessionRepository;
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.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringSessionWebSessionStore}.
*
* @author Rob Winch
* @author Vedran Pavic
*/
public class SpringSessionWebSessionStoreTests<S extends Session> {
@Mock
private ReactiveSessionRepository<S> sessionRepository;
@Mock
private S createSession;
@Mock
private S findByIdSession;
private SpringSessionWebSessionStore<S> webSessionStore;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
this.webSessionStore = new SpringSessionWebSessionStore<>(this.sessionRepository);
given(this.sessionRepository.findById(any()))
.willReturn(Mono.just(this.findByIdSession));
given(this.sessionRepository.createSession())
.willReturn(Mono.just(this.createSession));
}
@Test
public void constructorWhenNullRepositoryThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new SpringSessionWebSessionStore<S>(null))
.withMessage("reactiveSessionRepository cannot be null");
}
@Test
public void createSessionWhenNoAttributesThenNotStarted() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
assertThat(createdWebSession.isStarted()).isFalse();
}
@Test
public void createSessionWhenAddAttributeThenStarted() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
assertThat(createdWebSession.isStarted()).isTrue();
}
@Test
public void createSessionWhenGetAttributesAndSizeThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.size()).isEqualTo(0);
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
assertThat(attributes.size()).isEqualTo(1);
}
@Test
public void createSessionWhenGetAttributesAndIsEmptyThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.isEmpty()).isTrue();
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
assertThat(attributes.isEmpty()).isFalse();
}
@Test
public void createSessionWhenGetAttributesAndContainsKeyAndNotStringThenFalse() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.containsKey(1L)).isFalse();
}
@Test
public void createSessionWhenGetAttributesAndContainsKeyAndNotFoundThenFalse() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.containsKey("a")).isFalse();
}
@Test
public void createSessionWhenGetAttributesAndContainsKeyAndFoundThenTrue() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.containsKey("a")).isTrue();
}
@Test
public void createSessionWhenGetAttributesAndPutThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.put("a", "b");
verify(this.createSession).setAttribute("a", "b");
}
@Test
public void createSessionWhenGetAttributesAndPutNullThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.put("a", null);
verify(this.createSession).setAttribute("a", null);
}
@Test
public void createSessionWhenGetAttributesAndRemoveThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.remove("a");
verify(this.createSession).removeAttribute("a");
}
@Test
public void createSessionWhenGetAttributesAndPutAllThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.putAll(Collections.singletonMap("a", "b"));
verify(this.createSession).setAttribute("a", "b");
}
@Test
public void createSessionWhenGetAttributesAndClearThenDelegatesToCreateSession() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.clear();
verify(this.createSession).removeAttribute("a");
}
@Test
public void createSessionWhenGetAttributesAndKeySetThenDelegatesToCreateSession() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.keySet()).containsExactly("a");
}
@Test
public void createSessionWhenGetAttributesAndValuesThenDelegatesToCreateSession() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
given(this.createSession.getAttribute("a")).willReturn("b");
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.values()).containsExactly("b");
}
@Test
public void createSessionWhenGetAttributesAndEntrySetThenDelegatesToCreateSession() {
String attrName = "attrName";
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton(attrName));
String attrValue = "attrValue";
given(this.createSession.getAttribute(attrName)).willReturn(attrValue);
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
Set<Map.Entry<String, Object>> entries = attributes.entrySet();
assertThat(entries)
.containsExactly(new AbstractMap.SimpleEntry<>(attrName, attrValue));
}
@Test
public void retrieveSessionThenStarted() {
String id = "id";
WebSession retrievedWebSession = this.webSessionStore.retrieveSession(id).block();
assertThat(retrievedWebSession.isStarted()).isTrue();
verify(this.findByIdSession).setLastAccessedTime(any());
}
@Test
public void removeSessionWhenInvokedThenSessionSaved() {
String sessionId = "session-id";
given(this.sessionRepository.deleteById(sessionId)).willReturn(Mono.empty());
this.webSessionStore.removeSession(sessionId).block();
verify(this.sessionRepository).deleteById(sessionId);
}
@Test
public void setClockWhenNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.webSessionStore.setClock(null))
.withMessage("clock cannot be null");
}
@Test // gh-1114
public void createSessionThenSessionIsNotExpired() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
assertThat(createdWebSession.isExpired()).isFalse();
}
@Test // gh-1114
public void invalidateSessionThenSessionIsExpired() {
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
given(createdWebSession.invalidate()).willReturn(Mono.empty());
createdWebSession.invalidate().block();
assertThat(createdWebSession.isExpired()).isTrue();
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2014-2019 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.web.socket.handler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.session.web.socket.events.SessionConnectEvent;
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.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
public class WebSocketConnectHandlerDecoratorFactoryTests {
@Mock
ApplicationEventPublisher eventPublisher;
@Mock
WebSocketHandler delegate;
@Mock
WebSocketSession session;
@Captor
ArgumentCaptor<SessionConnectEvent> event;
WebSocketConnectHandlerDecoratorFactory factory;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
this.factory = new WebSocketConnectHandlerDecoratorFactory(this.eventPublisher);
}
@Test
public void constructorNullEventPublisher() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new WebSocketConnectHandlerDecoratorFactory(null))
.withMessage("eventPublisher cannot be null");
}
@Test
public void decorateAfterConnectionEstablished() throws Exception {
WebSocketHandler decorated = this.factory.decorate(this.delegate);
decorated.afterConnectionEstablished(this.session);
verify(this.eventPublisher).publishEvent(this.event.capture());
assertThat(this.event.getValue().getWebSocketSession()).isSameAs(this.session);
}
@Test
public void decorateAfterConnectionEstablishedEventError() throws Exception {
WebSocketHandler decorated = this.factory.decorate(this.delegate);
willThrow(new IllegalStateException("Test throw on publishEvent"))
.given(this.eventPublisher).publishEvent(any(ApplicationEvent.class));
decorated.afterConnectionEstablished(this.session);
verify(this.eventPublisher).publishEvent(any(SessionConnectEvent.class));
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2014-2019 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.web.socket.handler;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.session.MapSession;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.session.web.socket.events.SessionConnectEvent;
import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class WebSocketRegistryListenerTests {
@Mock
private WebSocketSession wsSession;
@Mock
private WebSocketSession wsSession2;
@Mock
private Message<byte[]> message;
@Mock
private Principal principal;
private SessionConnectEvent connect;
private SessionConnectEvent connect2;
private SessionDisconnectEvent disconnect;
private SessionDeletedEvent deleted;
private SessionExpiredEvent expired;
private Map<String, Object> attributes;
private WebSocketRegistryListener listener;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
String sessionId = "session-id";
MapSession session = new MapSession(sessionId);
this.attributes = new HashMap<>();
SessionRepositoryMessageInterceptor.setSessionId(this.attributes, sessionId);
given(this.wsSession.getAttributes()).willReturn(this.attributes);
given(this.wsSession.getPrincipal()).willReturn(this.principal);
given(this.wsSession.getId()).willReturn("wsSession-id");
given(this.wsSession2.getAttributes()).willReturn(this.attributes);
given(this.wsSession2.getPrincipal()).willReturn(this.principal);
given(this.wsSession2.getId()).willReturn("wsSession-id2");
Map<String, Object> headers = new HashMap<>();
headers.put(SimpMessageHeaderAccessor.SESSION_ATTRIBUTES, this.attributes);
given(this.message.getHeaders()).willReturn(new MessageHeaders(headers));
this.listener = new WebSocketRegistryListener();
this.connect = new SessionConnectEvent(this.listener, this.wsSession);
this.connect2 = new SessionConnectEvent(this.listener, this.wsSession2);
this.disconnect = new SessionDisconnectEvent(this.listener, this.message,
this.wsSession.getId(), CloseStatus.NORMAL);
this.deleted = new SessionDeletedEvent(this.listener, session);
this.expired = new SessionExpiredEvent(this.listener, session);
}
@Test
public void onApplicationEventConnectSessionDeleted() throws Exception {
this.listener.onApplicationEvent(this.connect);
this.listener.onApplicationEvent(this.deleted);
verify(this.wsSession).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
}
@Test
public void onApplicationEventConnectSessionExpired() throws Exception {
this.listener.onApplicationEvent(this.connect);
this.listener.onApplicationEvent(this.expired);
verify(this.wsSession).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
}
@Test
public void onApplicationEventConnectSessionDeletedNullPrincipal() throws Exception {
given(this.wsSession.getPrincipal()).willReturn(null);
this.listener.onApplicationEvent(this.connect);
this.listener.onApplicationEvent(this.deleted);
verify(this.wsSession, times(0)).close(any(CloseStatus.class));
}
@Test
public void onApplicationEventConnectDisconnect() throws Exception {
this.listener.onApplicationEvent(this.connect);
this.listener.onApplicationEvent(this.disconnect);
this.listener.onApplicationEvent(this.deleted);
verify(this.wsSession, times(0)).close(any(CloseStatus.class));
}
// gh-76
@Test
@SuppressWarnings("unchecked")
public void onApplicationEventConnectDisconnectCleanup() {
this.listener.onApplicationEvent(this.connect);
this.listener.onApplicationEvent(this.disconnect);
Map<String, Map<String, WebSocketSession>> httpSessionIdToWsSessions = (Map<String, Map<String, WebSocketSession>>) ReflectionTestUtils
.getField(this.listener, "httpSessionIdToWsSessions");
assertThat(httpSessionIdToWsSessions).isEmpty();
}
@Test
public void onApplicationEventConnectDisconnectNullSession() throws Exception {
this.listener.onApplicationEvent(this.connect);
this.attributes.clear();
this.listener.onApplicationEvent(this.disconnect);
// no exception
}
@Test
public void onApplicationEventConnectConnectDisonnect() throws Exception {
this.listener.onApplicationEvent(this.connect);
this.listener.onApplicationEvent(this.connect2);
this.listener.onApplicationEvent(this.disconnect);
this.listener.onApplicationEvent(this.deleted);
verify(this.wsSession2).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
verify(this.wsSession, times(0)).close(any(CloseStatus.class));
}
}

View File

@@ -0,0 +1,305 @@
/*
* Copyright 2014-2019 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.web.socket.server;
import java.time.Instant;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpSession;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.mock.web.MockHttpServletRequest;
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.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
public class SessionRepositoryMessageInterceptorTests {
@Mock
SessionRepository<Session> sessionRepository;
@Mock
MessageChannel channel;
@Mock
Session session;
Message<?> createMessage;
SimpMessageHeaderAccessor headers;
SessionRepositoryMessageInterceptor<Session> interceptor;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
this.interceptor = new SessionRepositoryMessageInterceptor<>(
this.sessionRepository);
this.headers = SimpMessageHeaderAccessor.create();
this.headers.setSessionId("session");
this.headers.setSessionAttributes(new HashMap<>());
setMessageType(SimpMessageType.MESSAGE);
String sessionId = "http-session";
setSessionId(sessionId);
given(this.sessionRepository.findById(sessionId)).willReturn(this.session);
}
@Test
public void preSendconstructorNullRepository() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new SessionRepositoryMessageInterceptor<>(null))
.withMessage("sessionRepository cannot be null");
}
@Test
public void preSendNullMessage() {
assertThat(this.interceptor.preSend(null, this.channel)).isNull();
}
@Test
public void preSendConnectAckDoesNotInvokeSessionRepository() {
setMessageType(SimpMessageType.CONNECT_ACK);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void preSendHeartbeatDoesNotInvokeSessionRepository() {
setMessageType(SimpMessageType.HEARTBEAT);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void preSendDisconnectDoesNotInvokeSessionRepository() {
setMessageType(SimpMessageType.DISCONNECT);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void preSendOtherDoesNotInvokeSessionRepository() {
setMessageType(SimpMessageType.OTHER);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void setMatchingMessageTypesNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.interceptor.setMatchingMessageTypes(null))
.withMessage("matchingMessageTypes cannot be null or empty");
}
@Test
public void setMatchingMessageTypesEmpty() {
assertThatIllegalArgumentException().isThrownBy(
() -> this.interceptor.setMatchingMessageTypes(Collections.emptySet()))
.withMessage("matchingMessageTypes cannot be null or empty");
}
@Test
public void preSendSetMatchingMessageTypes() {
this.interceptor.setMatchingMessageTypes(EnumSet.of(SimpMessageType.DISCONNECT));
setMessageType(SimpMessageType.DISCONNECT);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.sessionRepository).findById(anyString());
verify(this.sessionRepository).save(this.session);
}
@Test
public void preSendConnectUpdatesLastUpdateTime() {
setMessageType(SimpMessageType.CONNECT);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
@Test
public void preSendMessageUpdatesLastUpdateTime() {
setMessageType(SimpMessageType.MESSAGE);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
@Test
public void preSendSubscribeUpdatesLastUpdateTime() {
setMessageType(SimpMessageType.SUBSCRIBE);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
@Test
public void preSendUnsubscribeUpdatesLastUpdateTime() {
setMessageType(SimpMessageType.UNSUBSCRIBE);
this.session.setLastAccessedTime(Instant.EPOCH);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
// This will updated when SPR-12288 is resolved
@Test
public void preSendExpiredSession() {
setSessionId("expired");
this.interceptor.preSend(createMessage(), this.channel);
verify(this.sessionRepository, times(0)).save(any(Session.class));
}
@Test
public void preSendNullSessionId() {
setSessionId(null);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void preSendNullSessionAttributes() {
this.headers.setSessionAttributes(null);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void beforeHandshakeNotServletServerHttpRequest() throws Exception {
assertThat(this.interceptor.beforeHandshake(null, null, null, null)).isTrue();
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void beforeHandshakeNullSession() throws Exception {
ServletServerHttpRequest request = new ServletServerHttpRequest(
new MockHttpServletRequest());
assertThat(this.interceptor.beforeHandshake(request, null, null, null)).isTrue();
verifyZeroInteractions(this.sessionRepository);
}
@Test
public void beforeHandshakeSession() throws Exception {
MockHttpServletRequest httpRequest = new MockHttpServletRequest();
HttpSession httpSession = httpRequest.getSession();
ServletServerHttpRequest request = new ServletServerHttpRequest(httpRequest);
Map<String, Object> attributes = new HashMap<>();
assertThat(this.interceptor.beforeHandshake(request, null, null, attributes))
.isTrue();
assertThat(attributes.size()).isEqualTo(1);
assertThat(SessionRepositoryMessageInterceptor.getSessionId(attributes))
.isEqualTo(httpSession.getId());
}
/**
* At the moment there is no need for afterHandshake to do anything.
*/
@Test
public void afterHandshakeDoesNothing() {
this.interceptor.afterHandshake(null, null, null, null);
verifyZeroInteractions(this.sessionRepository);
}
private void setSessionId(String id) {
SessionRepositoryMessageInterceptor
.setSessionId(this.headers.getSessionAttributes(), id);
}
private Message<?> createMessage() {
this.createMessage = MessageBuilder.createMessage("",
this.headers.getMessageHeaders());
return this.createMessage;
}
private void setMessageType(SimpMessageType type) {
this.headers.setHeader(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER, type);
}
static AlmostNowMatcher isAlmostNow() {
return new AlmostNowMatcher();
}
static class AlmostNowMatcher implements ArgumentMatcher<Instant> {
@Override
public boolean matches(Instant argument) {
long now = System.currentTimeMillis();
long delta = now - argument.toEpochMilli();
return delta >= 0 && delta < TimeUnit.SECONDS.toMillis(3);
}
}
}

View File

@@ -0,0 +1,24 @@
apply plugin: 'io.spring.convention.spring-module'
description = "Spring Session Redis implementation"
dependencies {
compile project(':spring-session-core')
compile ("org.springframework.data:spring-data-redis") {
exclude group: "org.slf4j", module: 'slf4j-api'
exclude group: "org.slf4j", module: 'jcl-over-slf4j'
}
optional "io.projectreactor:reactor-core"
optional "org.springframework:spring-web"
testCompile "io.projectreactor:reactor-test"
testCompile "javax.servlet:javax.servlet-api"
testCompile "org.springframework:spring-web"
testCompile "org.springframework.security:spring-security-core"
testCompile "org.junit.jupiter:junit-jupiter-api"
testRuntime "org.junit.jupiter:junit-jupiter-engine"
integrationTestCompile "io.lettuce:lettuce-core"
integrationTestCompile "org.testcontainers:testcontainers"
}

View File

@@ -0,0 +1,71 @@
/*
* 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.data;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.context.ApplicationListener;
import org.springframework.session.events.AbstractSessionEvent;
public class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
private Map<String, AbstractSessionEvent> events = new HashMap<>();
private ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();
@Override
public void onApplicationEvent(AbstractSessionEvent event) {
String sessionId = event.getSessionId();
this.events.put(sessionId, event);
Object lock = getLock(sessionId);
synchronized (lock) {
lock.notifyAll();
}
}
public void clear() {
this.events.clear();
this.locks.clear();
}
public boolean receivedEvent(String sessionId) throws InterruptedException {
return waitForEvent(sessionId) != null;
}
@SuppressWarnings("unchecked")
public <E extends AbstractSessionEvent> E getEvent(String sessionId)
throws InterruptedException {
return (E) waitForEvent(sessionId);
}
@SuppressWarnings("unchecked")
private <E extends AbstractSessionEvent> E waitForEvent(String sessionId)
throws InterruptedException {
Object lock = getLock(sessionId);
synchronized (lock) {
if (!this.events.containsKey(sessionId)) {
lock.wait(10000);
}
}
return (E) this.events.get(sessionId);
}
private Object getLock(String sessionId) {
return this.locks.computeIfAbsent(sessionId, (k) -> new Object());
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2014-2019 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.data.redis;
import org.testcontainers.containers.GenericContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
/**
* Base class for {@link RedisOperationsSessionRepository} integration tests.
*
* @author Vedran Pavic
*/
public abstract class AbstractRedisITests {
private static final String DOCKER_IMAGE = "redis:5.0.5";
protected static class BaseConfig {
@Bean
public GenericContainer redisContainer() {
GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
.withExposedPorts(6379);
redisContainer.start();
return redisContainer;
}
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(
redisContainer().getContainerIpAddress(),
redisContainer().getFirstMappedPort());
return new LettuceConnectionFactory(configuration);
}
}
}

View File

@@ -0,0 +1,240 @@
/*
* Copyright 2014-2019 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.data.redis;
import java.time.Instant;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.Session;
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Integration tests for {@link ReactiveRedisOperationsSessionRepository}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests {
@Autowired
private ReactiveRedisOperationsSessionRepository repository;
@Test
public void saves() throws InterruptedException {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
String expectedAttributeName = "a";
String expectedAttributeValue = "b";
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
this.repository.save(toSave).block();
Session session = this.repository.findById(toSave.getId()).block();
assertThat(session.getId()).isEqualTo(toSave.getId());
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
assertThat(session.<String>getAttribute(expectedAttributeName))
.isEqualTo(toSave.getAttribute(expectedAttributeName));
this.repository.deleteById(toSave.getId()).block();
assertThat(this.repository.findById(toSave.getId()).block()).isNull();
}
@Test // gh-1399
public void saveMultipleTimes() {
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
.createSession().block();
session.setAttribute("attribute1", "value1");
Mono<Void> save1 = this.repository.save(session);
session.setAttribute("attribute2", "value2");
Mono<Void> save2 = this.repository.save(session);
Mono.zip(save1, save2).block();
}
@Test
public void putAllOnSingleAttrDoesNotRemoveOld() {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
toSave.setAttribute("a", "b");
this.repository.save(toSave).block();
toSave = this.repository.findById(toSave.getId()).block();
toSave.setAttribute("1", "2");
this.repository.save(toSave).block();
toSave = this.repository.findById(toSave.getId()).block();
Session session = this.repository.findById(toSave.getId()).block();
assertThat(session.getAttributeNames().size()).isEqualTo(2);
assertThat(session.<String>getAttribute("a")).isEqualTo("b");
assertThat(session.<String>getAttribute("1")).isEqualTo("2");
this.repository.deleteById(toSave.getId()).block();
}
@Test
public void changeSessionIdWhenOnlyChangeId() throws Exception {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
toSave.setAttribute(attrName, attrValue);
this.repository.save(toSave).block();
ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository
.findById(toSave.getId()).block();
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
String originalFindById = findById.getId();
String changeSessionId = findById.changeSessionId();
this.repository.save(findById).block();
assertThat(this.repository.findById(originalFindById).block()).isNull();
ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository
.findById(changeSessionId).block();
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
.isEqualTo(attrValue);
}
@Test
public void changeSessionIdWhenChangeTwice() throws Exception {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
this.repository.save(toSave).block();
String originalId = toSave.getId();
String changeId1 = toSave.changeSessionId();
String changeId2 = toSave.changeSessionId();
this.repository.save(toSave).block();
assertThat(this.repository.findById(originalId).block()).isNull();
assertThat(this.repository.findById(changeId1).block()).isNull();
assertThat(this.repository.findById(changeId2).block()).isNotNull();
}
@Test
public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
this.repository.save(toSave).block();
ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository
.findById(toSave.getId()).block();
findById.setAttribute(attrName, attrValue);
String originalFindById = findById.getId();
String changeSessionId = findById.changeSessionId();
this.repository.save(findById).block();
assertThat(this.repository.findById(originalFindById).block()).isNull();
ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository
.findById(changeSessionId).block();
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
.isEqualTo(attrValue);
}
@Test
public void changeSessionIdWhenHasNotSaved() throws Exception {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
String originalId = toSave.getId();
toSave.changeSessionId();
this.repository.save(toSave).block();
assertThat(this.repository.findById(toSave.getId()).block()).isNotNull();
assertThat(this.repository.findById(originalId).block()).isNull();
}
// gh-954
@Test
public void changeSessionIdSaveTwice() {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
String originalId = toSave.getId();
toSave.changeSessionId();
this.repository.save(toSave).block();
this.repository.save(toSave).block();
assertThat(this.repository.findById(toSave.getId()).block()).isNotNull();
assertThat(this.repository.findById(originalId).block()).isNull();
}
// gh-1111
@Test
public void changeSessionSaveOldSessionInstance() {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
String sessionId = toSave.getId();
this.repository.save(toSave).block();
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
.findById(sessionId).block();
session.changeSessionId();
session.setLastAccessedTime(Instant.now());
this.repository.save(session).block();
toSave.setLastAccessedTime(Instant.now());
assertThatIllegalStateException()
.isThrownBy(() -> this.repository.save(toSave).block())
.withMessage("Session was invalidated");
assertThat(this.repository.findById(sessionId).block()).isNull();
assertThat(this.repository.findById(session.getId()).block()).isNotNull();
}
@Configuration
@EnableRedisWebSession
static class Config extends BaseConfig {
}
}

View File

@@ -0,0 +1,641 @@
/*
* Copyright 2014-2019 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.data.redis;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.DefaultMessage;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.data.SessionEventRegistry;
import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
@Autowired
private RedisOperationsSessionRepository repository;
@Autowired
private SessionEventRegistry registry;
@SpringSessionRedisOperations
private RedisOperations<Object, Object> redis;
private SecurityContext context;
private SecurityContext changedContext;
@BeforeEach
public void setup() {
if (this.registry != null) {
this.registry.clear();
}
this.context = SecurityContextHolder.createEmptyContext();
this.context.setAuthentication(
new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(),
"na", AuthorityUtils.createAuthorityList("ROLE_USER")));
this.changedContext = SecurityContextHolder.createEmptyContext();
this.changedContext.setAuthentication(new UsernamePasswordAuthenticationToken(
"changedContext-" + UUID.randomUUID(), "na",
AuthorityUtils.createAuthorityList("ROLE_USER")));
}
@Test
public void saves() throws InterruptedException {
String username = "saves-" + System.currentTimeMillis();
String usernameSessionKey = "RedisOperationsSessionRepositoryITests:index:"
+ INDEX_NAME + ":" + username;
RedisSession toSave = this.repository.createSession();
String expectedAttributeName = "a";
String expectedAttributeValue = "b";
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username,
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
toSaveContext.setAuthentication(toSaveToken);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, toSaveContext);
toSave.setAttribute(INDEX_NAME, username);
this.registry.clear();
this.repository.save(toSave);
assertThat(this.registry.receivedEvent(toSave.getId())).isTrue();
assertThat(this.registry.<SessionCreatedEvent>getEvent(toSave.getId()))
.isInstanceOf(SessionCreatedEvent.class);
assertThat(this.redis.boundSetOps(usernameSessionKey).members())
.contains(toSave.getId());
Session session = this.repository.findById(toSave.getId());
assertThat(session.getId()).isEqualTo(toSave.getId());
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
assertThat(session.<String>getAttribute(expectedAttributeName))
.isEqualTo(toSave.getAttribute(expectedAttributeName));
this.registry.clear();
this.repository.deleteById(toSave.getId());
assertThat(this.repository.findById(toSave.getId())).isNull();
assertThat(this.registry.<SessionDestroyedEvent>getEvent(toSave.getId()))
.isInstanceOf(SessionDestroyedEvent.class);
assertThat(this.redis.boundSetOps(usernameSessionKey).members())
.doesNotContain(toSave.getId());
assertThat(this.registry.getEvent(toSave.getId()).getSession()
.<String>getAttribute(expectedAttributeName))
.isEqualTo(expectedAttributeValue);
}
@Test
public void putAllOnSingleAttrDoesNotRemoveOld() {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute("a", "b");
this.repository.save(toSave);
toSave = this.repository.findById(toSave.getId());
toSave.setAttribute("1", "2");
this.repository.save(toSave);
toSave = this.repository.findById(toSave.getId());
Session session = this.repository.findById(toSave.getId());
assertThat(session.getAttributeNames().size()).isEqualTo(2);
assertThat(session.<String>getAttribute("a")).isEqualTo("b");
assertThat(session.<String>getAttribute("1")).isEqualTo("2");
this.repository.deleteById(toSave.getId());
}
@Test
public void findByPrincipalName() throws Exception {
String principalName = "findByPrincipalName" + UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
this.repository.deleteById(toSave.getId());
assertThat(this.registry.receivedEvent(toSave.getId())).isTrue();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameExpireRemovesIndex() throws Exception {
String principalName = "findByPrincipalNameExpireRemovesIndex"
+ UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameNoPrincipalNameChange() throws Exception {
String principalName = "findByPrincipalNameNoPrincipalNameChange"
+ UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByPrincipalNameNoPrincipalNameChangeReload() throws Exception {
String principalName = "findByPrincipalNameNoPrincipalNameChangeReload"
+ UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave = this.repository.findById(toSave.getId());
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedPrincipalName() throws Exception {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute(INDEX_NAME, null);
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedPrincipalName() throws Exception {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalNameChanged);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedPrincipalNameReload() throws Exception {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
RedisSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedPrincipalNameReload() throws Exception {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
RedisSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(getSession);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalNameChanged);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findBySecurityPrincipalName() throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
this.repository.deleteById(toSave.getId());
assertThat(this.registry.receivedEvent(toSave.getId())).isTrue();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findBySecurityPrincipalNameExpireRemovesIndex() throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChange() throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChangeReload()
throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave = this.repository.findById(toSave.getId());
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedSecurityPrincipalName() throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, null);
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedSecurityPrincipalName() throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(toSave);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getChangedSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedSecurityPrincipalNameReload() throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
RedisSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedSecurityPrincipalNameReload() throws Exception {
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
RedisSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(getSession);
Map<String, RedisSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getChangedSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void changeSessionIdWhenOnlyChangeId() throws Exception {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
RedisSession toSave = this.repository.createSession();
toSave.setAttribute(attrName, attrValue);
this.repository.save(toSave);
RedisSession findById = this.repository.findById(toSave.getId());
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
String originalFindById = findById.getId();
String changeSessionId = findById.changeSessionId();
this.repository.save(findById);
assertThat(this.repository.findById(originalFindById)).isNull();
RedisSession findByChangeSessionId = this.repository.findById(changeSessionId);
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
}
@Test
public void changeSessionIdWhenChangeTwice() throws Exception {
RedisSession toSave = this.repository.createSession();
this.repository.save(toSave);
String originalId = toSave.getId();
String changeId1 = toSave.changeSessionId();
String changeId2 = toSave.changeSessionId();
this.repository.save(toSave);
assertThat(this.repository.findById(originalId)).isNull();
assertThat(this.repository.findById(changeId1)).isNull();
assertThat(this.repository.findById(changeId2)).isNotNull();
}
@Test
public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
RedisSession toSave = this.repository.createSession();
this.repository.save(toSave);
RedisSession findById = this.repository.findById(toSave.getId());
findById.setAttribute(attrName, attrValue);
String originalFindById = findById.getId();
String changeSessionId = findById.changeSessionId();
this.repository.save(findById);
assertThat(this.repository.findById(originalFindById)).isNull();
RedisSession findByChangeSessionId = this.repository.findById(changeSessionId);
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
}
@Test
public void changeSessionIdWhenHasNotSaved() throws Exception {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
RedisSession toSave = this.repository.createSession();
String originalId = toSave.getId();
toSave.changeSessionId();
this.repository.save(toSave);
assertThat(this.repository.findById(toSave.getId())).isNotNull();
assertThat(this.repository.findById(originalId)).isNull();
}
// gh-962
@Test
public void changeSessionIdSaveTwice() {
RedisSession toSave = this.repository.createSession();
String originalId = toSave.getId();
toSave.changeSessionId();
this.repository.save(toSave);
this.repository.save(toSave);
assertThat(this.repository.findById(toSave.getId())).isNotNull();
assertThat(this.repository.findById(originalId)).isNull();
}
// gh-1137
@Test
public void changeSessionIdWhenSessionIsDeleted() {
RedisSession toSave = this.repository.createSession();
String sessionId = toSave.getId();
this.repository.save(toSave);
this.repository.deleteById(sessionId);
toSave.changeSessionId();
this.repository.save(toSave);
assertThat(this.repository.findById(toSave.getId())).isNull();
assertThat(this.repository.findById(sessionId)).isNull();
}
@Test // gh-1270
public void changeSessionIdSaveConcurrently() {
RedisSession toSave = this.repository.createSession();
String originalId = toSave.getId();
this.repository.save(toSave);
RedisSession copy1 = this.repository.findById(originalId);
RedisSession copy2 = this.repository.findById(originalId);
copy1.changeSessionId();
this.repository.save(copy1);
copy2.changeSessionId();
this.repository.save(copy2);
assertThat(this.repository.findById(originalId)).isNull();
assertThat(this.repository.findById(copy1.getId())).isNotNull();
assertThat(this.repository.findById(copy2.getId())).isNull();
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}
private String getChangedSecurityName() {
return this.changedContext.getAuthentication().getName();
}
@Configuration
@EnableRedisHttpSession(redisNamespace = "RedisOperationsSessionRepositoryITests")
static class Config extends BaseConfig {
@Bean
public SessionEventRegistry sessionEventRegistry() {
return new SessionEventRegistry();
}
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright 2014-2019 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.data.redis;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.session.MapSession;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository.RedisSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Integration tests for {@link SimpleRedisOperationsSessionRepository}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
class SimpleRedisOperationsSessionRepositoryITests extends AbstractRedisITests {
@Autowired
private SimpleRedisOperationsSessionRepository sessionRepository;
@Test
void save_NewSession_ShouldSaveSession() {
RedisSession session = createAndSaveSession(Instant.now());
assertThat(session.getMaxInactiveInterval()).isEqualTo(
Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
assertThat(session.getAttributeNames())
.isEqualTo(Collections.singleton("attribute1"));
assertThat(session.<String>getAttribute("attribute1")).isEqualTo("value1");
}
@Test
void save_LastAccessedTimeInPast_ShouldExpireSession() {
assertThat(createAndSaveSession(Instant.EPOCH)).isNull();
}
@Test
void save_DeletedSession_ShouldThrowException() {
RedisSession session = createAndSaveSession(Instant.now());
this.sessionRepository.deleteById(session.getId());
assertThatIllegalStateException()
.isThrownBy(() -> this.sessionRepository.save(session))
.withMessage("Session was invalidated");
}
@Test
void save_ConcurrentUpdates_ShouldSaveSession() {
RedisSession copy1 = createAndSaveSession(Instant.now());
String sessionId = copy1.getId();
RedisSession copy2 = this.sessionRepository.findById(sessionId);
Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS);
updateSession(copy1, now.plusSeconds(1L), "attribute2", "value2");
this.sessionRepository.save(copy1);
updateSession(copy2, now.plusSeconds(2L), "attribute3", "value3");
this.sessionRepository.save(copy2);
RedisSession session = this.sessionRepository.findById(sessionId);
assertThat(session.getLastAccessedTime()).isEqualTo(now.plusSeconds(2L));
assertThat(session.getAttributeNames()).hasSize(3);
assertThat(session.<String>getAttribute("attribute1")).isEqualTo("value1");
assertThat(session.<String>getAttribute("attribute2")).isEqualTo("value2");
assertThat(session.<String>getAttribute("attribute3")).isEqualTo("value3");
}
@Test
void save_ChangeSessionIdAndUpdateAttribute_ShouldChangeSessionId() {
RedisSession session = createAndSaveSession(Instant.now());
String originalSessionId = session.getId();
updateSession(session, Instant.now(), "attribute1", "value2");
String newSessionId = session.changeSessionId();
this.sessionRepository.save(session);
RedisSession loaded = this.sessionRepository.findById(newSessionId);
assertThat(loaded).isNotNull();
assertThat(loaded.getAttributeNames()).hasSize(1);
assertThat(loaded.<String>getAttribute("attribute1")).isEqualTo("value2");
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
}
@Test
void save_OnlyChangeSessionId_ShouldChangeSessionId() {
RedisSession session = createAndSaveSession(Instant.now());
String originalSessionId = session.getId();
String newSessionId = session.changeSessionId();
this.sessionRepository.save(session);
assertThat(this.sessionRepository.findById(newSessionId)).isNotNull();
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
}
@Test
void save_ChangeSessionIdTwice_ShouldChangeSessionId() {
RedisSession session = createAndSaveSession(Instant.now());
String originalSessionId = session.getId();
updateSession(session, Instant.now(), "attribute1", "value2");
String newSessionId1 = session.changeSessionId();
updateSession(session, Instant.now(), "attribute1", "value3");
String newSessionId2 = session.changeSessionId();
this.sessionRepository.save(session);
assertThat(this.sessionRepository.findById(newSessionId1)).isNull();
assertThat(this.sessionRepository.findById(newSessionId2)).isNotNull();
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
}
@Test
void save_ChangeSessionIdOnNewSession_ShouldChangeSessionId() {
RedisSession session = this.sessionRepository.createSession();
String originalSessionId = session.getId();
updateSession(session, Instant.now(), "attribute1", "value1");
String newSessionId = session.changeSessionId();
this.sessionRepository.save(session);
assertThat(this.sessionRepository.findById(newSessionId)).isNotNull();
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
}
@Test
void save_ChangeSessionIdSaveTwice_ShouldChangeSessionId() {
RedisSession session = createAndSaveSession(Instant.now());
String originalSessionId;
originalSessionId = session.getId();
updateSession(session, Instant.now(), "attribute1", "value1");
String newSessionId = session.changeSessionId();
this.sessionRepository.save(session);
this.sessionRepository.save(session);
assertThat(this.sessionRepository.findById(newSessionId)).isNotNull();
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
}
@Test
void save_ChangeSessionIdOnDeletedSession_ShouldThrowException() {
RedisSession session = createAndSaveSession(Instant.now());
String originalSessionId = session.getId();
this.sessionRepository.deleteById(originalSessionId);
updateSession(session, Instant.now(), "attribute1", "value1");
String newSessionId = session.changeSessionId();
assertThatIllegalStateException()
.isThrownBy(() -> this.sessionRepository.save(session))
.withMessage("Session was invalidated");
assertThat(this.sessionRepository.findById(newSessionId)).isNull();
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
}
@Test
void save_ChangeSessionIdConcurrent_ShouldThrowException() {
RedisSession copy1 = createAndSaveSession(Instant.now());
String originalSessionId = copy1.getId();
RedisSession copy2 = this.sessionRepository.findById(originalSessionId);
Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS);
updateSession(copy1, now.plusSeconds(1L), "attribute2", "value2");
String newSessionId1 = copy1.changeSessionId();
this.sessionRepository.save(copy1);
updateSession(copy2, now.plusSeconds(2L), "attribute3", "value3");
String newSessionId2 = copy2.changeSessionId();
assertThatIllegalStateException()
.isThrownBy(() -> this.sessionRepository.save(copy2))
.withMessage("Session was invalidated");
assertThat(this.sessionRepository.findById(newSessionId1)).isNotNull();
assertThat(this.sessionRepository.findById(newSessionId2)).isNull();
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
}
@Test
void deleteById_ValidSession_ShouldDeleteSession() {
RedisSession session = createAndSaveSession(Instant.now());
this.sessionRepository.deleteById(session.getId());
assertThat(this.sessionRepository.findById(session.getId())).isNull();
}
@Test
void deleteById_DeletedSession_ShouldDoNothing() {
RedisSession session = createAndSaveSession(Instant.now());
this.sessionRepository.deleteById(session.getId());
this.sessionRepository.deleteById(session.getId());
assertThat(this.sessionRepository.findById(session.getId())).isNull();
}
@Test
void deleteById_NonexistentSession_ShouldDoNothing() {
String sessionId = UUID.randomUUID().toString();
this.sessionRepository.deleteById(sessionId);
assertThat(this.sessionRepository.findById(sessionId)).isNull();
}
private RedisSession createAndSaveSession(Instant lastAccessedTime) {
RedisSession session = this.sessionRepository.createSession();
session.setLastAccessedTime(lastAccessedTime);
session.setAttribute("attribute1", "value1");
this.sessionRepository.save(session);
return this.sessionRepository.findById(session.getId());
}
private static void updateSession(RedisSession session, Instant lastAccessedTime,
String attributeName, Object attributeValue) {
session.setLastAccessedTime(lastAccessedTime);
session.setAttribute(attributeName, attributeValue);
}
@Configuration
@EnableSpringHttpSession
static class Config extends BaseConfig {
@Bean
public SimpleRedisOperationsSessionRepository sessionRepository(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.afterPropertiesSet();
return new SimpleRedisOperationsSessionRepository(redisTemplate);
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2014-2019 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.data.redis.config.annotation.web.http;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.AbstractRedisITests;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
public class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Session>
extends AbstractRedisITests {
@Autowired
private SessionRepository<S> repository;
@Autowired
private SessionExpiredEventRegistry registry;
private final Object lock = new Object();
@BeforeEach
public void setup() {
this.registry.setLock(this.lock);
}
@Test
public void expireFiresSessionExpiredEvent() throws InterruptedException {
S toSave = this.repository.createSession();
toSave.setAttribute("a", "b");
Authentication toSaveToken = new UsernamePasswordAuthenticationToken("user",
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
toSaveContext.setAuthentication(toSaveToken);
toSave.setAttribute("SPRING_SECURITY_CONTEXT", toSaveContext);
this.repository.save(toSave);
synchronized (this.lock) {
this.lock.wait(toSave.getMaxInactiveInterval().plusMillis(1).toMillis());
}
if (!this.registry.receivedEvent()) {
// Redis makes no guarantees on when an expired event will be fired
// we can ensure it gets fired by trying to get the session
this.repository.findById(toSave.getId());
synchronized (this.lock) {
if (!this.registry.receivedEvent()) {
// wait at most a minute
this.lock.wait(TimeUnit.MINUTES.toMillis(1));
}
}
}
assertThat(this.registry.receivedEvent()).isTrue();
}
static class SessionExpiredEventRegistry
implements ApplicationListener<SessionExpiredEvent> {
private boolean receivedEvent;
private Object lock;
@Override
public void onApplicationEvent(SessionExpiredEvent event) {
synchronized (this.lock) {
this.receivedEvent = true;
this.lock.notifyAll();
}
}
public boolean receivedEvent() {
return this.receivedEvent;
}
public void setLock(Object lock) {
this.lock = lock;
}
}
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1)
static class Config extends BaseConfig {
@Bean
public SessionExpiredEventRegistry sessionDestroyedEventRegistry() {
return new SessionExpiredEventRegistry();
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2014-2019 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.data.redis.flushimmediately;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.AbstractRedisITests;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisOperationsSessionRepositoryFlushImmediatelyITests<S extends Session>
extends AbstractRedisITests {
@Autowired
private SessionRepository<S> sessionRepository;
@Test
public void savesOnCreate() throws InterruptedException {
S created = this.sessionRepository.createSession();
S getSession = this.sessionRepository.findById(created.getId());
assertThat(getSession).isNotNull();
}
@Configuration
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
static class RedisHttpSessionConfig extends BaseConfig {
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2014-2019 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.data.redis.taskexecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.data.redis.AbstractRedisITests;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Vladimir Tsanev
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisListenerContainerTaskExecutorITests extends AbstractRedisITests {
@Autowired
private SessionTaskExecutor executor;
@SpringSessionRedisOperations
private RedisOperations<Object, Object> redis;
@Test
public void testRedisDelEventsAreDispatchedInSessionTaskExecutor()
throws InterruptedException {
BoundSetOperations<Object, Object> ops = this.redis.boundSetOps(
"spring:session:RedisListenerContainerTaskExecutorITests:expirations:dummy");
ops.add("value");
ops.remove("value");
assertThat(this.executor.taskDispatched()).isTrue();
}
static class SessionTaskExecutor implements TaskExecutor {
private Object lock = new Object();
private final Executor executor;
private Boolean taskDispatched;
SessionTaskExecutor(Executor executor) {
this.executor = executor;
}
@Override
public void execute(Runnable task) {
synchronized (this.lock) {
try {
this.executor.execute(task);
}
finally {
this.taskDispatched = true;
this.lock.notifyAll();
}
}
}
public boolean taskDispatched() throws InterruptedException {
if (this.taskDispatched != null) {
return this.taskDispatched;
}
synchronized (this.lock) {
this.lock.wait(TimeUnit.SECONDS.toMillis(1));
}
return (this.taskDispatched != null) ? this.taskDispatched : Boolean.FALSE;
}
}
@Configuration
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
static class Config extends BaseConfig {
@Bean
public Executor springSessionRedisTaskExecutor() {
return new SessionTaskExecutor(Executors.newSingleThreadExecutor());
}
@Bean
public Executor springSessionRedisSubscriptionExecutor() {
return new SimpleAsyncTaskExecutor();
}
}
}

View File

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

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