Compare commits

...

48 Commits

Author SHA1 Message Date
Spring Buildmaster
774e6df063 Release version 1.3.5.RELEASE 2019-01-11 05:02:06 +00:00
Rob Winch
6911bd359b Update to Spring 4.3.19.RELEASE
Fixes: gh-1310
2019-01-10 22:00:27 -06:00
Rob Winch
ddae03c79e Update to Spring Security 4.2.11.RELEASE
Fixes: gh-1309
2019-01-10 21:45:49 -06:00
Vedran Pavic
bba095f276 Fix Spring Security integration docs sample
Resolves: #1306
2019-01-10 13:54:45 +01:00
Rob Winch
61937a9251 Fix SessionRepositoryFilter Javadoc 2018-12-14 09:45:41 -06:00
Jeff
3885b7e7ff Fix RedisOperationsSessionRepository javadoc typos
Resolves: #1276
2018-12-04 20:08:50 +01:00
Vedran Pavic
bea569c3c8 Make SessionUpdateEntryProcessor public
Resolves: #1266
2018-11-26 19:21:55 +01:00
Spring Buildmaster
7f1434cc4c Next development version 2018-11-14 16:51:17 +00:00
Spring Buildmaster
cd8f87e0a9 Release version 1.3.4.RELEASE 2018-11-14 16:51:11 +00:00
Vedran Pavic
95f41a7024 Polish 2018-11-08 22:32:46 +01:00
Vedran Pavic
d245cc1a36 Polish contribution
Resolves: #1250
2018-11-08 22:00:52 +01:00
Josh Cummings
695f2f1509 Commit Session on Include Dispatch
The servlet spec disallows any writing of headers after an include has been issued.

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

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

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

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

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

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

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

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

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

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

Closes gh-980
2018-01-29 16:26:08 +01:00
Vedran Pavic
b2d2335d73 Replace StringBuffer usages with StringBuilder
Closes gh-979
2018-01-29 16:25:59 +01:00
Spring Buildmaster
f597c5a824 Next development version 2017-04-27 18:57:32 +00:00
Spring Buildmaster
212eca306c Release version 1.3.1.RELEASE 2017-04-27 18:57:22 +00:00
73 changed files with 913 additions and 340 deletions

4
.gitignore vendored
View File

@@ -10,5 +10,5 @@ target
out
.springBeans
*.rdb
!eclispe/.checkstyle
.checkstyle
.checkstyle
!etc/eclipse/.checkstyle

View File

@@ -19,7 +19,7 @@ plugins {
group = 'org.springframework.session'
ext.springBootVersion = '1.4.2.RELEASE'
ext.springBootVersion = '1.4.7.RELEASE'
ext.IDE_GRADLE = "$rootDir/gradle/ide.gradle"
ext.JAVA_GRADLE = "$rootDir/gradle/java.gradle"
ext.SPRING3_GRADLE = "$rootDir/gradle/spring3.gradle"
@@ -51,12 +51,14 @@ sonarqube {
}
}
task configDocsZip(dependsOn: [':docs:asciidoctor',':spring-session:javadoc']) << {
project.tasks.docsZip.from(project(':docs').asciidoctor) {
into('reference')
}
project.tasks.docsZip.from(project(':spring-session').javadoc) {
into('api')
task configDocsZip(dependsOn: [':docs:asciidoctor',':spring-session:javadoc']) {
doLast {
project.tasks.docsZip.from(project(':docs').asciidoctor) {
into('reference')
}
project.tasks.docsZip.from(project(':spring-session').javadoc) {
into('api')
}
}
}

View File

@@ -25,7 +25,7 @@ If you are using Maven, ensure to add the following dependencies:
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
----

View File

@@ -127,3 +127,6 @@ Alternatively, you can also delete the explicit key. Enter the following into yo
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now visit the application at http://localhost:8080/test/index and observe that we are no longer authenticated.
NOTE: Spring Session will not work with grails flash scope without additional work. +
See this answer for an explanation: https://stackoverflow.com/a/43311427

View File

@@ -95,7 +95,7 @@ and managed by GemFire. As well, we have specified an arbitrary expiration attr
for when the Session will timeout, which is triggered by a GemFire Region entry expiration event that also invalidates
the Session object in the Region.
<2> Next, we define a few `Properties` that allow us to configure certain aspects of the GemFire Server using
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
http://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html[GemFire's System properties].
<3> Then, we create an instance of the GemFire `Cache` using our defined `Properties`.
<4> Finally, we configure and start a `CacheServer` running in the GemFire Server to listen for connections
from cache clients. The `CacheServer's` `Socket` will be used to connect our GemFire cache client,
@@ -159,7 +159,7 @@ and GemFire out-of-the-box using the following attributes:
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
with a GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`). This attribute is only used when configuring client Region.
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
@@ -230,7 +230,7 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
see http://gemfire.docs.pivotal.io/docs-gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
NOTE: In order to run the following, you must uncomment the lines in the `GemFireServer` class, `gemfireProperties` bean
for the following GemFire System properties: `jmx-manager` and `jmx-manager-start`.

View File

@@ -92,7 +92,7 @@ bean to replace placeholders in the Spring XML configuration meta-data with the
at the designated host/port is running and listening for client connections, blocking client startup until
the server is available and ready.
<4> Next, we include a `Properties` bean to configure certain aspects of the GemFire client cache using
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System Properties].
http://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html[GemFire's System Properties].
In this case, we are just setting GemFire's `log-level` from a application-specific System property, defaulting
to `warning` if unspecified.
<5> Then we create a instance of a GemFire `ClientCache` initialized with our `gemfireProperties`.
@@ -234,7 +234,7 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
see https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:

View File

@@ -87,12 +87,12 @@ include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/Clie
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session
and GemFire.
<2> Next, we register a `Properties` bean that allows us to configure certain aspects of the GemFire client cache
using http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
using http://gemfire.docs.pivotal.io/docs-gemfire/latest/reference/topics/gemfire_properties.html[GemFire's System properties].
<3> We use the `Properties` to configure an instance of a GemFire `ClientCache`.
<4> Then, we configure a `Pool` of client connections to talk to the GemFire Server in our Client/Server topology. In our
configuration, we have used sensible settings for timeouts, number of connections and so on. Also, the `Pool` has been
configured to connect directly to a server. Learn more about various `Pool` configuration settings from the
http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API].
http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API].
<56> Finally, we include a Spring `BeanPostProcessor` to block the client until our GemFire Server is up and running,
listening for and accepting client connections.
@@ -100,7 +100,7 @@ The `gemfireCacheServerReadyBeanPostProcessor` is necessary in order to coordina
an automated fashion during testing, but unnecessary in situations where the GemFire cluster is already presently
running, such as in production.
The `BeanPostProcessor` uses a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/management/membership/ClientMembershipListener.html[ClientMembershipListener]
The `BeanPostProcessor` uses a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/management/membership/ClientMembershipListener.html[ClientMembershipListener]
that will be notified when the client has successfully connected to the server. Once a connection has been established,
the listener releases the latch that the `BeanPostProcessor` will wait on (up to the specified timeout) in the
`postProcessAfterInitialization` callback to block the client.
@@ -118,7 +118,7 @@ and GemFire out-of-the-box using the following attributes:
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
with a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`). This attribute is only used when configuring client Region.
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
@@ -233,7 +233,7 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
see https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:

View File

@@ -176,7 +176,7 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
see http://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:

View File

@@ -104,7 +104,7 @@ and GemFire out-of-the-box using the following attributes:
* `maxInactiveIntervalInSeconds` - controls HttpSession idle-timeout expiration (defaults to **30 minutes**).
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "_ClusteredSpringSessions_").
* `serverRegionShort` - specifies GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies]
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
with a GemFire https://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
(default is `PARTITION`).
NOTE: `clientRegionShort` is ignored in a peer cache configuration and only applies when a client-server topology,
@@ -175,7 +175,7 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
see https://gemfire.docs.pivotal.io/gemfire/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
at the command-line:

View File

@@ -192,7 +192,7 @@ The two most common topologies to manage Spring Sessions using GemFire include:
* <<httpsession-gemfire-clientserver,Client-Server>>
* <<httpsession-gemfire-p2p,Peer-To-Peer (P2P)>>
Additionally, GemFire supports site-to-site replication using http://gemfire.docs.pivotal.io/docs-gemfire/topologies_and_comm/multi_site_configuration/chapter_overview.html[WAN functionality].
Additionally, GemFire supports site-to-site replication using http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/multi_site_configuration/chapter_overview.html[WAN functionality].
The ability to configure and use GemFire's WAN support is independent of Spring Session, and is beyond the scope
of this document. More details on GemFire WAN functionality can be found http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/#bootstrap:gateway[here].
@@ -829,12 +829,12 @@ EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
When a session expires key is deleted or expires, the keyspace notification triggers a lookup of the actual session and a SessionDestroyedEvent is fired.
One problem with relying on Redis expiration exclusively is that Redis makes no guarantee of when the expired event will be fired if they key has not been accessed.
One problem with relying on Redis expiration exclusively is that Redis makes no guarantee of when the expired event will be fired if the key has not been accessed.
Specifically the background task that Redis uses to clean up expired keys is a low priority task and may not trigger the key expiration.
For additional details see http://redis.io/topics/notifications[Timing of expired events] section in the Redis documentation.
To circumvent the fact that expired events are not guaranteed to happen we can ensure that each key is accessed when it is expected to expire.
This means that if the TTL is expired on the key, Redis will remove the key and fire the expired event when we try to access they key.
This means that if the TTL is expired on the key, Redis will remove the key and fire the expired event when we try to access the key.
For this reason, each session expiration is also tracked to the nearest minute.
This allows a background task to access the potentially expired sessions to ensure that Redis expired events are fired in a more deterministic fashion.
@@ -846,7 +846,7 @@ EXPIRE spring:session:expirations1439245080000 2100
----
The background task will then use these mappings to explicitly request each key.
By accessing they key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
By accessing the key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
[NOTE]
====
@@ -1199,8 +1199,17 @@ Spring Session is Open Source software released under the http://www.apache.org/
[[community-extensions]]
=== Community Extensions
https://github.com/maseev/spring-session-orientdb[Spring Session OrientDB]
http://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session[Spring Session Infinispan]
|===
| Name | Location
| Spring Session OrientDB
| https://github.com/maseev/spring-session-orientdb
| Spring Session Infinispan
| http://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session
|===
[[minimum-requirements]]
== Minimum Requirements

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
@@ -52,7 +51,7 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
// tag::rememberme-bean[]
@Bean
RememberMeServices rememberMeServices() {
public SpringSessionRememberMeServices rememberMeServices() {
SpringSessionRememberMeServices rememberMeServices =
new SpringSessionRememberMeServices();
// optionally customize

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* 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.
@@ -21,7 +21,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
@@ -33,7 +32,7 @@ import org.springframework.session.security.SpringSessionBackedSessionRegistry;
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;
private FindByIndexNameSessionRepository sessionRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
@@ -45,7 +44,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
}
@Bean
SpringSessionBackedSessionRegistry sessionRegistry() {
@SuppressWarnings("unchecked")
public SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry(this.sessionRepository);
}
}

View File

@@ -59,7 +59,7 @@ cleanup_profile=_Spring Boot Cleanup Conventions
cleanup_settings_version=2
eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_Spring Boot Java Conventions
formatter_profile=Spring Session Java Conventions
formatter_settings_version=12
org.eclipse.jdt.ui.exception.name=e
org.eclipse.jdt.ui.gettersetter.use.is=true

View File

@@ -1,32 +1,31 @@
assertjVersion=2.5.0
bootstrapVersion=2.3.2
commonsLoggingVersion=1.2
commonsPoolVersion=2.4.2
gebVersion=0.13.1
groovyVersion=2.4.4
h2Version=1.4.192
hazelcastVersion=3.6.5
html5ShivVersion=3.7.3
httpClientVersion=4.5.1
jacksonVersion=2.6.5
jedisVersion=2.8.1
jacksonVersion=2.8.8
jspApiVersion=2.0
jstlVersion=1.2.1
jstlelVersion=1.2.5
junitVersion=4.12
lettuceVersion=3.5.0.Final
mockitoVersion=1.10.19
seleniumVersion=2.52.0
servletApiVersion=3.0.1
spockVersion=1.0-groovy-2.4
springVersion=4.3.4.RELEASE
springDataGemFireVersion=1.8.10.RELEASE
springDataGeodeVersion=1.0.0.INCUBATING-RELEASE
springDataMongoVersion=1.9.4.RELEASE
springDataRedisVersion=1.7.10.RELEASE
# We cannot go beyond this version with Spring 3 support
jstlelVersion=1.2.5
version=1.3.5.RELEASE
springDataRedisVersion=1.7.11.RELEASE
html5ShivVersion=3.7.3
commonsLoggingVersion=1.2
junitVersion=4.12
springDataRedisSpring3Version=1.7.1.RELEASE
springSecurityVersion=4.2.0.RELEASE
lettuceVersion=3.5.0.Final
gebVersion=0.13.1
mockitoVersion=1.10.19
hazelcastVersion=3.6.8
seleniumVersion=2.52.0
springDataGeodeVersion=1.0.0.INCUBATING-RELEASE
springSecurityVersion=4.2.11.RELEASE
springVersion=4.3.19.RELEASE
httpClientVersion=4.5.3
h2Version=1.4.195
jedisVersion=2.8.2
springDataMongoVersion=1.9.11.RELEASE
springShellVersion=1.1.0.RELEASE
springDataGemFireVersion=1.8.11.RELEASE
assertjVersion=2.5.0
spockVersion=1.0-groovy-2.4
webjarsTaglibVersion=0.3
version=1.3.1.BUILD-SNAPSHOT
jstlVersion=1.2.1
groovyVersion=2.4.11

View File

@@ -78,7 +78,7 @@ task integrationTest(type: Test, dependsOn: jar) {
check.dependsOn integrationTest
checkstyle {
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
configFile = rootProject.file('etc/checkstyle/checkstyle.xml')
configProperties.configDir = configFile.parentFile
toolVersion = '6.16.1'
}

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Fri Nov 11 19:46:57 CET 2016
#Mon Jan 29 18:35:20 CET 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-bin.zip

19
gradlew vendored
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -154,16 +154,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 "$@")
# 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
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
exec "$JAVACMD" "$@"

View File

@@ -14,9 +14,11 @@ apply from: SAMPLE_GRADLE
group = 'samples'
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session'),
"org.springframework.boot:spring-boot-starter-redis",
"org.springframework.boot:spring-boot-starter-data-redis",
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-thymeleaf",
"org.springframework.boot:spring-boot-starter-security",

View File

@@ -14,9 +14,11 @@ apply from: SAMPLE_GRADLE
group = 'samples'
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session'),
"org.springframework.boot:spring-boot-starter-redis",
"org.springframework.boot:spring-boot-starter-data-redis",
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-security",
"org.springframework.boot:spring-boot-starter-thymeleaf",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,7 +42,8 @@ public class SessionConfig {
int port = SocketUtils.findAvailableTcpPort();
config.getNetworkConfig()
.setPort(port);
.setPort(port)
.getJoin().getMulticastConfig().setEnabled(false);
System.out.println("Hazelcast port #: " + port);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,6 +52,7 @@ public class Initializer implements ServletContextListener {
Config cfg = new Config();
NetworkConfig netConfig = new NetworkConfig();
netConfig.setPort(getAvailablePort());
netConfig.getJoin().getMulticastConfig().setEnabled(false);
cfg.setNetworkConfig(netConfig);
SerializerConfig serializer = new SerializerConfig().setTypeClass(Object.class)
.setImplementation(new ObjectStreamSerializer());

View File

@@ -10,12 +10,9 @@ buildscript {
apply from: JAVA_GRADLE
apply plugin: "application"
apply plugin: 'org.springframework.boot'
apply from: SAMPLE_GRADLE
tasks.findByPath("artifactoryPublish")?.enabled = false
sonarqube {
skipProject = true
}
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session-data-gemfire'),
@@ -24,14 +21,14 @@ dependencies {
"org.webjars:bootstrap:$bootstrapVersion",
"org.webjars:webjars-locator"
runtime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
runtime "org.springframework.shell:spring-shell:$springShellVersion"
testCompile "org.springframework.boot:spring-boot-starter-test"
integrationTestCompile gebDependencies,
"org.spockframework:spock-spring:$spockVersion"
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
integrationTestRuntime "org.springframework.shell:spring-shell:$springShellVersion"
}
run {
@@ -44,24 +41,26 @@ springBoot {
mainClass = 'sample.client.Application'
}
task runGemFireServer() << {
println 'STARTING GEMFIRE SERVER...'
task runGemFireServer() {
doLast {
println 'STARTING GEMFIRE SERVER...'
ext.port = reservePort()
ext.port = reservePort()
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
String[] commandLine = ['java', '-server', '-ea', '-classpath', classpath,
"-Dgemfire.cache.server.port=$port",
"-Dgemfire.log-level=" + System.getProperty('gemfire.log.level', 'warning'),
'sample.server.GemFireServer']
String[] commandLine = ['java', '-server', '-ea', '-classpath', classpath,
"-Dgemfire.cache.server.port=$port",
"-Dgemfire.log-level=" + System.getProperty('gemfire.log.level', 'warning'),
'sample.server.GemFireServer']
//println commandLine
//println commandLine
ext.process = commandLine.execute()
process.in.close()
process.out.close()
process.err.close()
ext.process = commandLine.execute()
process.in.close()
process.out.close()
process.err.close()
}
}

View File

@@ -1,12 +1,7 @@
apply from: JAVA_GRADLE
apply from: TOMCAT_7_GRADLE
apply plugin: "application"
tasks.findByPath("artifactoryPublish")?.enabled = false
sonarqube {
skipProject = true
}
apply from: SAMPLE_GRADLE
dependencies {
compile project(':spring-session-data-gemfire'),
@@ -17,13 +12,13 @@ dependencies {
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
runtime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
runtime "org.springframework.shell:spring-shell:$springShellVersion"
testCompile "junit:junit:$junitVersion"
integrationTestCompile gebDependencies
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
integrationTestRuntime "org.springframework.shell:spring-shell:$springShellVersion"
}
mainClassName = 'sample.Application'
@@ -31,29 +26,33 @@ mainClassName = 'sample.Application'
def port
def process
task availablePort() << {
def serverSocket = new ServerSocket(0)
port = serverSocket.localPort
serverSocket.close()
task availablePort() {
doLast {
def serverSocket = new ServerSocket(0)
port = serverSocket.localPort
serverSocket.close()
}
}
task runGemFireServer(dependsOn: availablePort) << {
println 'STARTING GEMFIRE SERVER...'
task runGemFireServer(dependsOn: availablePort) {
doLast {
println 'STARTING GEMFIRE SERVER...'
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
String[] commandLine = ['java', '-server', '-ea',
"-Dspring.session.data.gemfire.port=$port",
"-Dsample.httpsession.gemfire.log-level="
+ System.getProperty('sample.httpsession.gemfire.log-level', 'warning'),
'-classpath', classpath, 'sample.Application' ]
String[] commandLine = ['java', '-server', '-ea',
"-Dspring.session.data.gemfire.port=$port",
"-Dsample.httpsession.gemfire.log-level="
+ System.getProperty('sample.httpsession.gemfire.log-level', 'warning'),
'-classpath', classpath, 'sample.Application']
//println commandLine
//println commandLine
process = commandLine.execute()
process.in.close()
process.out.close()
process.err.close()
process = commandLine.execute()
process.in.close()
process.out.close()
process.err.close()
}
}
integrationTest.doLast {

View File

@@ -12,13 +12,13 @@ dependencies {
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
runtime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
runtime "org.springframework.shell:spring-shell:$springShellVersion"
testCompile "junit:junit:$junitVersion"
integrationTestCompile gebDependencies
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
integrationTestRuntime "org.springframework.shell:spring-shell:$springShellVersion"
}
@@ -27,29 +27,33 @@ def process
mainClassName = "sample.ServerConfig"
task availablePort() << {
def serverSocket = new ServerSocket(0)
port = serverSocket.localPort
serverSocket.close()
task availablePort() {
doLast {
def serverSocket = new ServerSocket(0)
port = serverSocket.localPort
serverSocket.close()
}
}
task runGemFireServer(dependsOn: availablePort) << {
println 'STARTING GEMFIRE SERVER...'
task runGemFireServer(dependsOn: availablePort) {
doLast {
println 'STARTING GEMFIRE SERVER...'
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
String[] commandLine = ['java', '-server', '-ea',
"-Dspring.session.data.gemfire.port=$port",
"-Dsample.httpsession.gemfire.log-level="
+ System.getProperty('sample.httpsession.gemfire.log-level', 'warning'),
'-classpath', classpath, 'sample.ServerConfig']
String[] commandLine = ['java', '-server', '-ea',
"-Dspring.session.data.gemfire.port=$port",
"-Dsample.httpsession.gemfire.log-level="
+ System.getProperty('sample.httpsession.gemfire.log-level', 'warning'),
'-classpath', classpath, 'sample.ServerConfig']
//println commandLine
//println commandLine
process = commandLine.execute()
process.in.close()
process.out.close()
process.err.close()
process = commandLine.execute()
process.in.close()
process.out.close()
process.err.close()
}
}
integrationTest.doLast {

View File

@@ -15,5 +15,5 @@ dependencies {
integrationTestCompile gebDependencies
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
integrationTestRuntime "org.springframework.shell:spring-shell:$springShellVersion"
}

View File

@@ -15,5 +15,5 @@ dependencies {
integrationTestCompile gebDependencies
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
integrationTestRuntime "org.springframework.shell:spring-shell:$springShellVersion"
}

View File

@@ -14,6 +14,8 @@ apply from: SAMPLE_GRADLE
group = 'samples'
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session-jdbc'),
"org.springframework.boot:spring-boot-starter-jdbc",

View File

@@ -10,20 +10,18 @@ buildscript {
apply plugin: 'org.springframework.boot'
apply from: JAVA_GRADLE
//tasks.findByPath("artifactoryPublish")?.enabled = false
apply from: SAMPLE_GRADLE
group = 'samples'
ext {
jsonassertVersion="1.3.0"
assertjVersion = "2.4.0"
}
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session'),
"org.springframework.boot:spring-boot-starter-redis",
"org.springframework.boot:spring-boot-starter-data-redis",
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-thymeleaf",
"org.springframework.boot:spring-boot-starter-security",
@@ -66,4 +64,4 @@ def reservePort() {
def result = socket.localPort
socket.close()
result
}
}

View File

@@ -14,6 +14,8 @@ apply from: SAMPLE_GRADLE
group = 'samples'
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session'),
"org.springframework.boot:spring-boot-starter-data-mongodb",

View File

@@ -15,6 +15,8 @@ apply from: SAMPLE_GRADLE
group = 'samples'
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'

View File

@@ -23,7 +23,6 @@ include 'samples:security'
include 'samples:users'
include 'samples:websocket'
include 'samples:mongo'
include 'samples:grails3'
include 'spring-session'
include 'spring-session-data-gemfire'

View File

@@ -29,14 +29,14 @@ dependencies {
"org.springframework.security:spring-security-web:$springSecurityVersion"
provided "javax.servlet:javax.servlet-api:$servletApiVersion"
integrationTestCompile "redis.clients:jedis:$jedisVersion",
"org.apache.commons:commons-pool2:2.2",
"org.apache.commons:commons-pool2:$commonsPoolVersion",
"com.hazelcast:hazelcast-client:$hazelcastVersion",
"com.h2database:h2:$h2Version",
"org.hsqldb:hsqldb:2.3.3",
"org.apache.derby:derby:10.12.1.1",
"de.flapdoodle.embed:de.flapdoodle.embed.mongo:1.50.2"
"de.flapdoodle.embed:de.flapdoodle.embed.mongo:1.50.5"
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
integrationTestRuntime "org.springframework.shell:spring-shell:$springShellVersion"
testCompile "junit:junit:$junitVersion",
"org.mockito:mockito-core:$mockitoVersion",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,17 @@ package org.springframework.session.hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.instance.HazelcastInstanceProxy;
import org.junit.Assume;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
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.MapSession;
import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession;
@@ -34,8 +42,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public abstract class AbstractHazelcastRepositoryITests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
@Autowired
private HazelcastInstance hazelcast;
private HazelcastInstance hazelcastInstance;
@Autowired
private HazelcastSessionRepository repository;
@@ -45,8 +55,8 @@ public abstract class AbstractHazelcastRepositoryITests {
HazelcastSession sessionToSave = this.repository.createSession();
String sessionId = sessionToSave.getId();
IMap<String, MapSession> hazelcastMap = this.hazelcast.getMap(
"spring:session:sessions");
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
.getMap("spring:session:sessions");
assertThat(hazelcastMap.size()).isEqualTo(0);
@@ -60,4 +70,70 @@ public abstract class AbstractHazelcastRepositoryITests {
assertThat(hazelcastMap.size()).isEqualTo(0);
}
@Test // gh-1076
public void attemptToUpdateSessionAfterDelete() {
HazelcastSession session = this.repository.createSession();
String sessionId = session.getId();
this.repository.save(session);
session = this.repository.getSession(sessionId);
session.setAttribute("attributeName", "attributeValue");
this.repository.delete(sessionId);
this.repository.save(session);
assertThat(this.repository.getSession(sessionId)).isNull();
}
@Test
public void createAndUpdateSession() {
HazelcastSession session = this.repository.createSession();
String sessionId = session.getId();
this.repository.save(session);
session = this.repository.getSession(sessionId);
session.setAttribute("attributeName", "attributeValue");
this.repository.save(session);
assertThat(this.repository.getSession(sessionId)).isNotNull();
}
@Test
public void createSessionWithSecurityContextAndFindById() {
HazelcastSession session = this.repository.createSession();
String sessionId = session.getId();
Authentication authentication = new UsernamePasswordAuthenticationToken(
"saves-" + System.currentTimeMillis(), "password",
AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
session.setAttribute(SPRING_SECURITY_CONTEXT, securityContext);
this.repository.save(session);
assertThat(this.repository.getSession(sessionId)).isNotNull();
}
@Test
public void createSessionWithSecurityContextAndFindByPrincipal() {
Assume.assumeTrue("Hazelcast runs in embedded server topology",
this.hazelcastInstance instanceof HazelcastInstanceProxy);
HazelcastSession session = this.repository.createSession();
String username = "saves-" + System.currentTimeMillis();
Authentication authentication = new UsernamePasswordAuthenticationToken(username,
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
session.setAttribute(SPRING_SECURITY_CONTEXT, securityContext);
this.repository.save(session);
assertThat(this.repository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username))
.isNotNull();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,12 +49,12 @@ public class HazelcastClientRepositoryITests extends AbstractHazelcastRepository
private static HazelcastInstance hazelcastInstance;
@BeforeClass
public static void setup() {
public static void setUpClass() {
hazelcastInstance = HazelcastITestUtils.embeddedHazelcastServer(PORT);
}
@AfterClass
public static void teardown() {
public static void tearDownClass() {
if (hazelcastInstance != null) {
hazelcastInstance.shutdown();
}
@@ -65,7 +65,7 @@ public class HazelcastClientRepositoryITests extends AbstractHazelcastRepository
static class HazelcastSessionConfig {
@Bean
public HazelcastInstance embeddedHazelcastClient() {
public HazelcastInstance hazelcastInstance() {
ClientConfig clientConfig = new ClientConfig();
clientConfig.getNetworkConfig().addAddress("127.0.0.1:" + PORT);
return HazelcastClient.newHazelcastClient(clientConfig);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.session.hazelcast;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapAttributeConfig;
import com.hazelcast.config.MapIndexConfig;
import com.hazelcast.config.NetworkConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
@@ -46,8 +47,12 @@ public final class HazelcastITestUtils {
Config config = new Config();
config.getNetworkConfig()
.setPort(port);
NetworkConfig networkConfig = config.getNetworkConfig();
networkConfig.setPort(port);
networkConfig.getJoin()
.getMulticastConfig().setEnabled(false);
config.getMapConfig("spring:session:sessions")
.addMapAttributeConfig(attributeConfig)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ public class HazelcastServerRepositoryITests extends AbstractHazelcastRepository
static class HazelcastSessionConfig {
@Bean
public HazelcastInstance embeddedHazelcastServer() {
public HazelcastInstance hazelcastInstance() {
return HazelcastITestUtils.embeddedHazelcastServer();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,10 +34,8 @@ 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.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -82,6 +80,19 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
AuthorityUtils.createAuthorityList("ROLE_USER")));
}
@Test
public void saveWhenNoAttributesThenCanBeFound() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.getSession(toSave.getId());
assertThat(session).isNotNull();
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
@Test
public void saves() throws InterruptedException {
String username = "saves-" + System.currentTimeMillis();
@@ -100,9 +111,11 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
this.repository.save(toSave);
Session session = this.repository.getSession(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository.getSession(toSave.getId());
assertThat(session.getId()).isEqualTo(toSave.getId());
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
assertThat(session.getAttribute(expectedAttributeName))
.isEqualTo(toSave.getAttribute(expectedAttributeName));
@@ -135,7 +148,9 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
Session session = this.repository.getSession(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository.getSession(toSave.getId());
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
assertThat(session.getAttributeNames().size()).isEqualTo(2);
assertThat(session.getAttribute("a")).isEqualTo("b");
assertThat(session.getAttribute("1")).isEqualTo("2");
@@ -156,9 +171,11 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setLastAccessedTime(lastAccessedTime);
this.repository.save(toSave);
ExpiringSession session = this.repository.getSession(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository.getSession(toSave.getId());
assertThat(session).isNotNull();
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
assertThat(session.isExpired()).isFalse();
assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessedTime);
}
@@ -225,6 +242,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -247,6 +268,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -289,6 +314,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -336,6 +365,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -395,6 +428,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -416,6 +453,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -455,6 +496,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -499,6 +544,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
for (JdbcOperationsSessionRepository.JdbcSession session : findByPrincipalName.values()) {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
}
}
@Test
@@ -559,6 +608,46 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.getSession(session.getId())).isNull();
}
@Test // gh-1031
public void saveDeleted() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.getSession(session.getId());
this.repository.delete(session.getId());
session.setLastAccessedTime(System.currentTimeMillis());
this.repository.save(session);
assertThat(this.repository.getSession(session.getId())).isNull();
}
@Test // gh-1031
public void saveDeletedAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.getSession(session.getId());
this.repository.delete(session.getId());
session.setLastAccessedTime(System.currentTimeMillis());
session.setAttribute("testName", "testValue1");
this.repository.save(session);
assertThat(this.repository.getSession(session.getId())).isNull();
}
@Test // gh-1203
public void saveWithLargeAttribute() {
String attributeName = "largeAttribute";
int arraySize = 4000;
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute(attributeName, new byte[arraySize]);
this.repository.save(session);
session = this.repository.getSession(session.getId());
assertThat(session).isNotNull();
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -14,16 +14,7 @@
<ports>0</ports>
</outbound-ports>
<join>
<multicast enabled="false">
</multicast>
<tcp-ip enabled="true">
<interface>127.0.0.1</interface>
<member-list>
<member>127.0.0.1</member>
</member-list>
</tcp-ip>
<aws enabled="false">
</aws>
<multicast enabled="false"/>
</join>
</network>

View File

@@ -14,16 +14,7 @@
<ports>0</ports>
</outbound-ports>
<join>
<multicast enabled="false">
</multicast>
<tcp-ip enabled="true">
<interface>127.0.0.1</interface>
<member-list>
<member>127.0.0.1</member>
</member-list>
</tcp-ip>
<aws enabled="false">
</aws>
<multicast enabled="false"/>
</join>
</network>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,7 +52,7 @@ public interface FindByIndexNameSessionRepository<S extends Session>
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the value of
* the specified principal name.
*
* @param indexName the name if the index (i.e.
* @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 Map (never null) of the session id to the {@link Session} of all sessions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.session;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -141,7 +142,7 @@ public final class MapSession implements ExpiringSession, Serializable {
}
public Set<String> getAttributeNames() {
return this.sessionAttrs.keySet();
return new HashSet<String>(this.sessionAttrs.keySet());
}
public void setAttribute(String attributeName, Object attributeValue) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -201,7 +201,7 @@ import org.springframework.util.Assert;
*
* <p>
* One problem with relying on Redis expiration exclusively is that Redis makes no
* guarantee of when the expired event will be fired if they key has not been accessed.
* guarantee of when the expired event will be fired if the key has not been accessed.
* Specifically the background task that Redis uses to clean up expired keys is a low
* priority task and may not trigger the key expiration. For additional details see
* <a href="http://redis.io/topics/notifications">Timing of expired events</a> section in
@@ -212,7 +212,7 @@ import org.springframework.util.Assert;
* To circumvent the fact that expired events are not guaranteed to happen we can ensure
* that each key is accessed when it is expected to expire. This means that if the TTL is
* expired on the key, Redis will remove the key and fire the expired event when we try to
* access they key.
* access the key.
* </p>
*
* <p>
@@ -277,7 +277,7 @@ public class RedisOperationsSessionRepository implements
static final String LAST_ACCESSED_ATTR = "lastAccessedTime";
/**
* The prefix of the key for used for session attributes. The suffix is the name of
* The prefix of the key used for session attributes. The suffix is the name of
* the session attribute. For example, if the session contained an attribute named
* attributeName, then there would be an entry in the hash named
* sessionAttr:attributeName that mapped to its value.
@@ -518,6 +518,12 @@ public class RedisOperationsSessionRepository implements
RedisSession session = getSession(sessionId, true);
if (session == null) {
logger.warn("Unable to publish SessionDestroyedEvent for session "
+ sessionId);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Publishing SessionDestroyedEvent for session " + sessionId);
}
@@ -525,20 +531,15 @@ public class RedisOperationsSessionRepository implements
cleanupPrincipalIndex(session);
if (isDeleted) {
handleDeleted(sessionId, session);
handleDeleted(session);
}
else {
handleExpired(sessionId, session);
handleExpired(session);
}
return;
}
}
private void cleanupPrincipalIndex(RedisSession session) {
if (session == null) {
return;
}
String sessionId = session.getId();
String principal = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(session);
if (principal != null) {
@@ -553,22 +554,12 @@ public class RedisOperationsSessionRepository implements
publishEvent(new SessionCreatedEvent(this, session));
}
private void handleDeleted(String sessionId, RedisSession session) {
if (session == null) {
publishEvent(new SessionDeletedEvent(this, sessionId));
}
else {
publishEvent(new SessionDeletedEvent(this, session));
}
private void handleDeleted(RedisSession session) {
publishEvent(new SessionDeletedEvent(this, session));
}
private void handleExpired(String sessionId, RedisSession session) {
if (session == null) {
publishEvent(new SessionExpiredEvent(this, sessionId));
}
else {
publishEvent(new SessionExpiredEvent(this, session));
}
private void handleExpired(RedisSession session) {
publishEvent(new SessionExpiredEvent(this, session));
}
private void publishEvent(ApplicationEvent event) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -108,8 +108,7 @@ import org.springframework.util.Assert;
*/
public class HazelcastSessionRepository implements
FindByIndexNameSessionRepository<HazelcastSessionRepository.HazelcastSession>,
EntryAddedListener<String, MapSession>,
EntryEvictedListener<String, MapSession>,
EntryAddedListener<String, MapSession>, EntryEvictedListener<String, MapSession>,
EntryRemovedListener<String, MapSession> {
/**
@@ -200,11 +199,24 @@ public class HazelcastSessionRepository implements
}
public void save(HazelcastSession session) {
if (session.isChanged()) {
this.sessions.put(session.getId(), session.getDelegate(),
if (session.isNew) {
this.sessions.set(session.getId(), session.getDelegate(),
session.getMaxInactiveIntervalInSeconds(), TimeUnit.SECONDS);
session.markUnchanged();
}
else if (session.hasChanges()) {
SessionUpdateEntryProcessor entryProcessor = new SessionUpdateEntryProcessor();
if (session.lastAccessedTimeChanged) {
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
}
if (session.maxInactiveIntervalChanged) {
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveIntervalInSeconds());
}
if (!session.delta.isEmpty()) {
entryProcessor.setDelta(session.delta);
}
this.sessions.executeOnKey(session.getId(), entryProcessor);
}
session.clearChangeFlags();
}
public HazelcastSession getSession(String id) {
@@ -223,13 +235,13 @@ public class HazelcastSessionRepository implements
this.sessions.remove(id);
}
public Map<String, HazelcastSession> findByIndexNameAndIndexValue(
String indexName, String indexValue) {
public Map<String, HazelcastSession> findByIndexNameAndIndexValue(String indexName,
String indexValue) {
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
return Collections.emptyMap();
}
Collection<MapSession> sessions = this.sessions.values(
Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue));
Collection<MapSession> sessions = this.sessions
.values(Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue));
Map<String, HazelcastSession> sessionMap = new HashMap<String, HazelcastSession>(
sessions.size());
for (MapSession session : sessions) {
@@ -270,7 +282,16 @@ public class HazelcastSessionRepository implements
final class HazelcastSession implements ExpiringSession {
private final MapSession delegate;
private boolean changed;
private boolean isNew;
private boolean sessionIdChanged;
private boolean lastAccessedTimeChanged;
private boolean maxInactiveIntervalChanged;
private Map<String, Object> delta = new HashMap<String, Object>();
/**
* Creates a new instance ensuring to mark all of the new attributes to be
@@ -278,7 +299,7 @@ public class HazelcastSessionRepository implements
*/
HazelcastSession() {
this(new MapSession());
this.changed = true;
this.isNew = true;
flushImmediateIfNecessary();
}
@@ -294,7 +315,7 @@ public class HazelcastSessionRepository implements
public void setLastAccessedTime(long lastAccessedTime) {
this.delegate.setLastAccessedTime(lastAccessedTime);
this.changed = true;
this.lastAccessedTimeChanged = true;
flushImmediateIfNecessary();
}
@@ -316,7 +337,7 @@ public class HazelcastSessionRepository implements
public void setMaxInactiveIntervalInSeconds(int interval) {
this.delegate.setMaxInactiveIntervalInSeconds(interval);
this.changed = true;
this.maxInactiveIntervalChanged = true;
flushImmediateIfNecessary();
}
@@ -334,28 +355,33 @@ public class HazelcastSessionRepository implements
public void setAttribute(String attributeName, Object attributeValue) {
this.delegate.setAttribute(attributeName, attributeValue);
this.changed = true;
this.delta.put(attributeName, attributeValue);
flushImmediateIfNecessary();
}
public void removeAttribute(String attributeName) {
this.delegate.removeAttribute(attributeName);
this.changed = true;
this.delta.put(attributeName, null);
flushImmediateIfNecessary();
}
boolean isChanged() {
return this.changed;
}
void markUnchanged() {
this.changed = false;
}
MapSession getDelegate() {
return this.delegate;
}
boolean hasChanges() {
return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged
|| !this.delta.isEmpty());
}
void clearChangeFlags() {
this.isNew = false;
this.lastAccessedTimeChanged = false;
this.sessionIdChanged = false;
this.maxInactiveIntervalChanged = false;
this.delta.clear();
}
private void flushImmediateIfNecessary() {
if (HazelcastSessionRepository.this.hazelcastFlushMode == HazelcastFlushMode.IMMEDIATE) {
HazelcastSessionRepository.this.save(this);

View File

@@ -0,0 +1,85 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.hazelcast;
import java.util.Map;
import com.hazelcast.map.AbstractEntryProcessor;
import com.hazelcast.map.EntryProcessor;
import org.springframework.session.MapSession;
/**
* Hazelcast {@link EntryProcessor} responsible for handling updates to session.
*
* @author Vedran Pavic
* @since 1.3.4
* @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession)
*/
public class SessionUpdateEntryProcessor
extends AbstractEntryProcessor<String, MapSession> {
private long lastAccessedTime;
private boolean lastAccessedTimeSet;
private int maxInactiveInterval;
private boolean maxInactiveIntervalSet;
private Map<String, Object> delta;
public Object process(Map.Entry<String, MapSession> entry) {
MapSession value = entry.getValue();
if (value == null) {
return Boolean.FALSE;
}
if (this.lastAccessedTimeSet) {
value.setLastAccessedTime(this.lastAccessedTime);
}
if (this.maxInactiveIntervalSet) {
value.setMaxInactiveIntervalInSeconds(this.maxInactiveInterval);
}
if (this.delta != null) {
for (final Map.Entry<String, Object> attribute : this.delta.entrySet()) {
if (attribute.getValue() != null) {
value.setAttribute(attribute.getKey(), attribute.getValue());
}
else {
value.removeAttribute(attribute.getKey());
}
}
}
entry.setValue(value);
return Boolean.TRUE;
}
void setLastAccessedTime(long lastAccessedTime) {
this.lastAccessedTime = lastAccessedTime;
this.lastAccessedTimeSet = true;
}
void setMaxInactiveInterval(int maxInactiveInterval) {
this.maxInactiveInterval = maxInactiveInterval;
this.maxInactiveIntervalSet = true;
}
void setDelta(Map<String, Object> delta) {
this.delta = delta;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,6 +105,7 @@ import org.springframework.util.StringUtils;
* );
*
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
* CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
*
* CREATE TABLE SPRING_SESSION_ATTRIBUTES (
* SESSION_ID CHAR(36),
@@ -142,7 +143,9 @@ public class JdbcOperationsSessionRepository implements
private static final String CREATE_SESSION_ATTRIBUTE_QUERY =
"INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " +
"VALUES (?, ?, ?)";
"SELECT SESSION_ID, ?, ? " +
"FROM %TABLE_NAME% " +
"WHERE SESSION_ID = ?";
private static final String GET_SESSION_QUERY =
"SELECT S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
@@ -187,8 +190,7 @@ public class JdbcOperationsSessionRepository implements
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<ExpiringSession>> extractor =
new ExpiringSessionResultSetExtractor();
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
/**
* The name of database table used by Spring Session to store sessions.
@@ -398,9 +400,9 @@ public class JdbcOperationsSessionRepository implements
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.getId());
ps.setString(2, attributeName);
serialize(ps, 3, session.getAttribute(attributeName));
ps.setString(1, attributeName);
serialize(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
}
public int getBatchSize() {
@@ -465,9 +467,9 @@ public class JdbcOperationsSessionRepository implements
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, session.getId());
ps.setString(2, entry.getKey());
serialize(ps, 3, entry.getValue());
ps.setString(1, entry.getKey());
serialize(ps, 2, entry.getValue());
ps.setString(3, session.getId());
}
});
@@ -483,10 +485,10 @@ public class JdbcOperationsSessionRepository implements
}
public JdbcSession getSession(final String id) {
final ExpiringSession session = this.transactionOperations.execute(new TransactionCallback<ExpiringSession>() {
final JdbcSession session = this.transactionOperations.execute(new TransactionCallback<JdbcSession>() {
public ExpiringSession doInTransaction(TransactionStatus status) {
List<ExpiringSession> sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query(
public JdbcSession doInTransaction(TransactionStatus status) {
List<JdbcSession> sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query(
JdbcOperationsSessionRepository.this.getSessionQuery,
new PreparedStatementSetter() {
@@ -510,7 +512,7 @@ public class JdbcOperationsSessionRepository implements
delete(id);
}
else {
return new JdbcSession(session);
return session;
}
}
return null;
@@ -533,9 +535,9 @@ public class JdbcOperationsSessionRepository implements
return Collections.emptyMap();
}
List<ExpiringSession> sessions = this.transactionOperations.execute(new TransactionCallback<List<ExpiringSession>>() {
List<JdbcSession> sessions = this.transactionOperations.execute(new TransactionCallback<List<JdbcSession>>() {
public List<ExpiringSession> doInTransaction(TransactionStatus status) {
public List<JdbcSession> doInTransaction(TransactionStatus status) {
return JdbcOperationsSessionRepository.this.jdbcOperations.query(
JdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery,
new PreparedStatementSetter() {
@@ -554,8 +556,8 @@ public class JdbcOperationsSessionRepository implements
Map<String, JdbcSession> sessionMap = new HashMap<String, JdbcSession>(
sessions.size());
for (ExpiringSession session : sessions) {
sessionMap.put(session.getId(), new JdbcSession(session));
for (JdbcSession session : sessions) {
sessionMap.put(session.getId(), session);
}
return sessionMap;
@@ -763,33 +765,33 @@ public class JdbcOperationsSessionRepository implements
}
private class ExpiringSessionResultSetExtractor
implements ResultSetExtractor<List<ExpiringSession>> {
private class SessionResultSetExtractor implements ResultSetExtractor<List<JdbcSession>> {
public List<ExpiringSession> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<ExpiringSession> sessions = new ArrayList<ExpiringSession>();
public List<JdbcSession> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<JdbcSession> sessions = new ArrayList<JdbcSession>();
while (rs.next()) {
String id = rs.getString("SESSION_ID");
MapSession session;
JdbcSession session;
if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) {
session = (MapSession) getLast(sessions);
session = getLast(sessions);
}
else {
session = new MapSession(id);
session.setCreationTime(rs.getLong("CREATION_TIME"));
session.setLastAccessedTime(rs.getLong("LAST_ACCESS_TIME"));
session.setMaxInactiveIntervalInSeconds(rs.getInt("MAX_INACTIVE_INTERVAL"));
MapSession delegate = new MapSession(id);
delegate.setCreationTime(rs.getLong("CREATION_TIME"));
delegate.setLastAccessedTime(rs.getLong("LAST_ACCESS_TIME"));
delegate.setMaxInactiveIntervalInSeconds(rs.getInt("MAX_INACTIVE_INTERVAL"));
session = new JdbcSession(delegate);
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
session.setAttribute(attributeName, deserialize(rs, "ATTRIBUTE_BYTES"));
session.delegate.setAttribute(attributeName, deserialize(rs, "ATTRIBUTE_BYTES"));
}
sessions.add(session);
}
return sessions;
}
private ExpiringSession getLast(List<ExpiringSession> sessions) {
private JdbcSession getLast(List<JdbcSession> sessions) {
return sessions.get(sessions.size() - 1);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,8 +33,10 @@ import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
@@ -85,7 +87,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
@Bean
public JdbcOperationsSessionRepository sessionRepository(
@Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations,
@Qualifier("springSessionJdbcOperations") JdbcTemplate jdbcOperations,
PlatformTransactionManager transactionManager) {
JdbcOperationsSessionRepository sessionRepository =
new JdbcOperationsSessionRepository(jdbcOperations, transactionManager);
@@ -98,6 +100,11 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
if (this.lobHandler != null) {
sessionRepository.setLobHandler(this.lobHandler);
}
else if (requiresTemporaryLob(jdbcOperations.getDataSource())) {
DefaultLobHandler lobHandler = new DefaultLobHandler();
lobHandler.setCreateTemporaryLob(true);
sessionRepository.setLobHandler(lobHandler);
}
if (this.springSessionConversionService != null) {
sessionRepository.setConversionService(this.springSessionConversionService);
}
@@ -111,11 +118,22 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
return sessionRepository;
}
private static boolean requiresTemporaryLob(DataSource dataSource) {
try {
String productName = (String) JdbcUtils.extractDatabaseMetaData(dataSource,
"getDatabaseProductName");
return "Oracle".equalsIgnoreCase(JdbcUtils.commonDatabaseName(productName));
}
catch (MetaDataAccessException ex) {
return false;
}
}
/**
* This must be a separate method because some ClassLoaders load the entire method
* definition even if an if statement guards against it loading. This means that older
* versions of Spring would cause a NoSuchMethodError if this were defined in
* {@link #sessionRepository(JdbcOperations, PlatformTransactionManager)}.
* {@link #sessionRepository(JdbcTemplate, PlatformTransactionManager)}.
*
* @return the default {@link ConversionService}
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -250,18 +250,18 @@ public final class CookieHttpSessionStrategy
return sessionIds.values().iterator().next();
}
StringBuffer buffer = new StringBuffer();
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sessionIds.entrySet()) {
String alias = entry.getKey();
String id = entry.getValue();
buffer.append(alias);
buffer.append(this.serializationDelimiter);
buffer.append(id);
buffer.append(this.serializationDelimiter);
sb.append(alias);
sb.append(this.serializationDelimiter);
sb.append(id);
sb.append(this.serializationDelimiter);
}
buffer.deleteCharAt(buffer.length() - 1);
return buffer.toString();
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
public void onInvalidateSession(HttpServletRequest request,

View File

@@ -26,6 +26,9 @@ 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}.
*
@@ -35,6 +38,8 @@ import javax.servlet.http.HttpServletResponse;
*/
public class DefaultCookieSerializer implements CookieSerializer {
private static final Log logger = LogFactory.getLog(DefaultCookieSerializer.class);
private String cookieName = "SESSION";
private Boolean useSecureCookie;
@@ -138,6 +143,7 @@ public class DefaultCookieSerializer implements CookieSerializer {
return new String(decodedCookieBytes);
}
catch (Exception e) {
logger.debug("Unable to Base64 decode value: " + base64Value);
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,8 +22,11 @@ import java.util.HashMap;
import java.util.Map;
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;
@@ -48,7 +51,7 @@ import org.springframework.session.SessionRepository;
* {@link org.springframework.session.SessionRepository}.
*
* The {@link SessionRepositoryFilter} uses a {@link HttpSessionStrategy} (default
* {@link CookieHttpSessionStrategy} to bridge logic between an
* {@link CookieHttpSessionStrategy}) to bridge logic between an
* {@link javax.servlet.http.HttpSession} and the
* {@link org.springframework.session.Session} abstraction. Specifically:
*
@@ -72,6 +75,7 @@ import org.springframework.session.SessionRepository;
* @param <S> the {@link ExpiringSession} type.
* @since 1.0
* @author Rob Winch
* @author Josh Cummings
*/
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends ExpiringSession>
@@ -289,9 +293,10 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
setCurrentSession(null);
HttpSessionWrapper newSession = getSession();
int originalMaxInactiveInterval = session.getMaxInactiveInterval();
original.setSession(newSession.getSession());
newSession.setMaxInactiveInterval(session.getMaxInactiveInterval());
newSession.setMaxInactiveInterval(originalMaxInactiveInterval);
for (Map.Entry<String, Object> attr : attrs.entrySet()) {
String attrName = attr.getKey();
Object attrValue = attr.getValue();
@@ -396,6 +401,12 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
.getRequestedSessionId(this);
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
RequestDispatcher requestDispatcher = super.getRequestDispatcher(path);
return new SessionCommittingRequestDispatcher(requestDispatcher);
}
/**
* Allows creating an HttpSession from a Session instance.
*
@@ -416,6 +427,34 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
SessionRepositoryFilter.this.sessionRepository.delete(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;
}
public void forward(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
this.delegate.forward(request, response);
}
public void include(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
SessionRepositoryRequestWrapper.this.commitSession();
this.delegate.include(request, response);
}
}
}
/**

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
) ENGINE=InnoDB;
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -8,6 +8,7 @@ CREATE TABLE SPRING_SESSION (
) LOCK DATAROWS;
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,4 +64,18 @@ public class MapSessionRepositoryTests {
assertThat(session.getMaxInactiveIntervalInSeconds())
.isEqualTo(expectedMaxInterval);
}
@Test // gh-1120
public void getAttributeNamesAndRemove() {
ExpiringSession 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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,6 +85,18 @@ public class MapSessionTests {
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 ExpiringSession {
public long getCreationTime() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,6 +59,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -529,6 +530,104 @@ public class RedisOperationsSessionRepositoryTests {
verify(this.defaultSerializer).deserialize(body);
}
@Test
public void onMessageDeletedSessionFound() throws Exception {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
Map map = map(RedisOperationsSessionRepository.MAX_INACTIVE_ATTR, 0,
RedisOperationsSessionRepository.LAST_ACCESSED_ATTR,
System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5));
given(this.boundHashOperations.entries()).willReturn(map);
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
verify(this.publisher).publishEvent(this.event.capture());
assertThat(this.event.getValue().getSessionId()).isEqualTo(deletedId);
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
}
@Test
public void onMessageDeletedSessionNotFound() throws Exception {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
given(this.boundHashOperations.entries()).willReturn(map());
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
}
@Test
public void onMessageExpiredSessionFound() throws Exception {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
Map map = map(RedisOperationsSessionRepository.MAX_INACTIVE_ATTR, 1,
RedisOperationsSessionRepository.LAST_ACCESSED_ATTR,
System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5));
given(this.boundHashOperations.entries()).willReturn(map);
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
verify(this.publisher).publishEvent(this.event.capture());
assertThat(this.event.getValue().getSessionId()).isEqualTo(expiredId);
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
}
@Test
public void onMessageExpiredSessionNotFound() throws Exception {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
given(this.boundHashOperations.entries()).willReturn(map());
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
}
@Test
public void resolvePrincipalIndex() {
PrincipalNameResolver resolver = RedisOperationsSessionRepository.PRINCIPAL_NAME_RESOLVER;
@@ -706,6 +805,19 @@ public class RedisOperationsSessionRepositoryTests {
this.redisRepository.setRedisFlushMode(null);
}
@Test // gh-1120
public void getAttributeNamesAndRemove() {
RedisSession session = this.redisRepository.new RedisSession(this.cached);
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
for (String attributeName : session.getAttributeNames()) {
session.removeAttribute(attributeName);
}
assertThat(session.getAttributeNames()).isEmpty();
}
private String getKey(String id) {
return "spring:session:sessions:" + id;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.hazelcast.core.IMap;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.query.impl.predicates.EqualPredicate;
import org.junit.Before;
import org.junit.Rule;
@@ -40,6 +41,7 @@ import org.springframework.session.hazelcast.HazelcastSessionRepository.Hazelcas
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
@@ -104,8 +106,8 @@ public class HazelcastSessionRepositoryTests {
verifyZeroInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
}
@Test
@@ -113,8 +115,8 @@ public class HazelcastSessionRepositoryTests {
this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE);
HazelcastSession session = this.repository.createSession();
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
}
@Test
@@ -124,8 +126,8 @@ public class HazelcastSessionRepositoryTests {
verifyZeroInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
}
@Test
@@ -134,8 +136,10 @@ public class HazelcastSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setAttribute("testName", "testValue");
verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()),
any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
@@ -148,8 +152,8 @@ public class HazelcastSessionRepositoryTests {
verifyZeroInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
}
@Test
@@ -158,8 +162,10 @@ public class HazelcastSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.removeAttribute("testName");
verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()),
any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
@@ -172,8 +178,8 @@ public class HazelcastSessionRepositoryTests {
verifyZeroInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
}
@Test
@@ -182,8 +188,10 @@ public class HazelcastSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setLastAccessedTime(System.currentTimeMillis());
verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()),
any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
@@ -196,8 +204,8 @@ public class HazelcastSessionRepositoryTests {
verifyZeroInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
}
@Test
@@ -206,8 +214,10 @@ public class HazelcastSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setMaxInactiveIntervalInSeconds(1);
verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()),
any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
@@ -217,8 +227,8 @@ public class HazelcastSessionRepositoryTests {
public void saveUnchangedFlushModeOnSave() {
HazelcastSession session = this.repository.createSession();
this.repository.save(session);
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
@@ -229,8 +239,8 @@ public class HazelcastSessionRepositoryTests {
this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE);
HazelcastSession session = this.repository.createSession();
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()),
isA(Long.class), eq(TimeUnit.SECONDS));
verify(this.sessions, times(1)).set(eq(session.getId()),
eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
@@ -249,8 +259,8 @@ public class HazelcastSessionRepositoryTests {
@Test
public void getSessionExpired() {
MapSession expired = new MapSession();
expired.setLastAccessedTime(System.currentTimeMillis() -
(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000));
expired.setLastAccessedTime(System.currentTimeMillis()
- (MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000));
given(this.sessions.get(eq(expired.getId()))).willReturn(expired);
HazelcastSession session = this.repository.getSession(expired.getId());
@@ -286,8 +296,8 @@ public class HazelcastSessionRepositoryTests {
public void findByIndexNameAndIndexValueUnknownIndexName() {
String indexValue = "testIndexValue";
Map<String, HazelcastSession> sessions = this.repository.findByIndexNameAndIndexValue(
"testIndexName", indexValue);
Map<String, HazelcastSession> sessions = this.repository
.findByIndexNameAndIndexValue("testIndexName", indexValue);
assertThat(sessions).isEmpty();
verifyZeroInteractions(this.sessions);
@@ -297,8 +307,10 @@ public class HazelcastSessionRepositoryTests {
public void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
String principal = "username";
Map<String, HazelcastSession> sessions = this.repository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
Map<String, HazelcastSession> sessions = this.repository
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal);
assertThat(sessions).isEmpty();
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
@@ -318,11 +330,26 @@ public class HazelcastSessionRepositoryTests {
saved.add(saved2);
given(this.sessions.values(isA(EqualPredicate.class))).willReturn(saved);
Map<String, HazelcastSession> sessions = this.repository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
Map<String, HazelcastSession> sessions = this.repository
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal);
assertThat(sessions).hasSize(2);
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
}
@Test // gh-1120
public void getAttributeNamesAndRemove() {
HazelcastSession 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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -412,7 +412,7 @@ public class JdbcOperationsSessionRepositoryTests {
@Test
public void getSessionExpired() {
MapSession expired = new MapSession();
JdbcOperationsSessionRepository.JdbcSession expired = this.repository.new JdbcSession();
expired.setLastAccessedTime(System.currentTimeMillis() -
(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000));
given(this.jdbcOperations.query(isA(String.class),
@@ -432,7 +432,8 @@ public class JdbcOperationsSessionRepositoryTests {
@Test
public void getSessionFound() {
MapSession saved = new MapSession();
JdbcOperationsSessionRepository.JdbcSession saved = this.repository.new JdbcSession(
new MapSession());
saved.setAttribute("savedName", "savedValue");
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
@@ -493,11 +494,12 @@ public class JdbcOperationsSessionRepositoryTests {
String principal = "username";
Authentication authentication = new UsernamePasswordAuthenticationToken(principal,
"notused", AuthorityUtils.createAuthorityList("ROLE_USER"));
List<MapSession> saved = new ArrayList<MapSession>(2);
MapSession saved1 = new MapSession();
List<JdbcOperationsSessionRepository.JdbcSession> saved =
new ArrayList<JdbcOperationsSessionRepository.JdbcSession>(2);
JdbcOperationsSessionRepository.JdbcSession saved1 = this.repository.new JdbcSession();
saved1.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
saved.add(saved1);
MapSession saved2 = new MapSession();
JdbcOperationsSessionRepository.JdbcSession saved2 = this.repository.new JdbcSession();
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
saved.add(saved2);
given(this.jdbcOperations.query(isA(String.class),
@@ -523,6 +525,19 @@ public class JdbcOperationsSessionRepositoryTests {
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong());
}
@Test // gh-1120
public void getAttributeNamesAndRemove() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
for (String attributeName : session.getAttributeNames()) {
session.removeAttribute(attributeName);
}
assertThat(session.getAttributeNames()).isEmpty();
}
private void assertPropagationRequiresNew() {
ArgumentCaptor<TransactionDefinition> argument =
ArgumentCaptor.forClass(TransactionDefinition.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -710,19 +710,19 @@ public class CookieHttpSessionStrategyTests {
}
private String createSessionCookieValue(long size) {
StringBuffer buffer = new StringBuffer();
StringBuilder sb = new StringBuilder();
for (long i = 0; i < size; i++) {
String hex = Long.toHexString(i);
buffer.append(hex);
buffer.append(" ");
buffer.append(i);
sb.append(hex);
sb.append(" ");
sb.append(i);
if (i < size - 1) {
buffer.append(" ");
sb.append(" ");
}
}
return buffer.toString();
return sb.toString();
}
@SuppressWarnings("deprecation")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -568,6 +568,27 @@ public class SessionRepositoryFilterTests {
});
}
// gh-951
@Test
public void doFilterChangeSessionIdCopyAttributes() throws Exception {
// change the session id
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setMaxInactiveInterval(300);
String originalSessionId = session.getId();
int originalMaxInactiveInterval = session.getMaxInactiveInterval();
String changeSessionId = ReflectionTestUtils.invokeMethod(wrappedRequest,
"changeSessionId");
assertThat(changeSessionId).isNotEqualTo(originalSessionId);
assertThat(session.getMaxInactiveInterval())
.isEqualTo(originalMaxInactiveInterval);
}
});
}
// gh-142, gh-153
@Test
public void doFilterIsRequestedValidSessionFalseInvalidId() throws Exception {
@@ -1154,6 +1175,22 @@ public class SessionRepositoryFilterTests {
});
}
@Test // gh-1243
public void doFilterInclude() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest,
HttpServletResponse wrappedResponse)
throws IOException, ServletException {
String id = wrappedRequest.getSession().getId();
wrappedRequest.getRequestDispatcher("/").include(wrappedRequest,
wrappedResponse);
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
}
});
}
// --- MultiHttpSessionStrategyAdapter
@Test