Compare commits

..

17 Commits

Author SHA1 Message Date
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
38 changed files with 453 additions and 134 deletions

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"

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,11 +1,11 @@
bootstrapVersion=2.3.2
commonsPoolVersion=2.4.2
jacksonVersion=2.6.5
jacksonVersion=2.8.8
jspApiVersion=2.0
servletApiVersion=3.0.1
jstlelVersion=1.2.5
version=1.3.3.RELEASE
springDataRedisVersion=1.7.10.RELEASE
version=1.3.4.RELEASE
springDataRedisVersion=1.7.11.RELEASE
html5ShivVersion=3.7.3
commonsLoggingVersion=1.2
junitVersion=4.12
@@ -13,19 +13,19 @@ springDataRedisSpring3Version=1.7.1.RELEASE
lettuceVersion=3.5.0.Final
gebVersion=0.13.1
mockitoVersion=1.10.19
hazelcastVersion=3.6.5
hazelcastVersion=3.6.8
seleniumVersion=2.52.0
springDataGeodeVersion=1.0.0.INCUBATING-RELEASE
springSecurityVersion=4.2.0.RELEASE
springVersion=4.3.4.RELEASE
httpClientVersion=4.5.1
h2Version=1.4.192
jedisVersion=2.8.1
springDataMongoVersion=1.9.4.RELEASE
springSecurityVersion=4.2.8.RELEASE
springVersion=4.3.9.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.10.RELEASE
springDataGemFireVersion=1.8.11.RELEASE
assertjVersion=2.5.0
spockVersion=1.0-groovy-2.4
webjarsTaglibVersion=0.3
jstlVersion=1.2.1
groovyVersion=2.4.4
groovyVersion=2.4.11

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-redis",

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-redis",

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

@@ -12,6 +12,8 @@ apply plugin: "application"
apply plugin: 'org.springframework.boot'
apply from: SAMPLE_GRADLE
ext['spring-security.version'] = springSecurityVersion
dependencies {
compile project(':spring-session-data-gemfire'),
"org.springframework.boot:spring-boot-starter-thymeleaf",
@@ -19,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 {

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"
}
mainClassName = 'sample.Application'

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

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

@@ -15,7 +15,6 @@ apply from: SAMPLE_GRADLE
group = 'samples'
ext {
jsonassertVersion="1.3.0"
assertjVersion = "2.4.0"
}
ext['spring-security.version'] = springSecurityVersion

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

@@ -608,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.
@@ -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

@@ -28,8 +28,6 @@ import javax.annotation.PreDestroy;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.IMap;
import com.hazelcast.map.AbstractEntryProcessor;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.map.listener.EntryAddedListener;
import com.hazelcast.map.listener.EntryEvictedListener;
import com.hazelcast.map.listener.EntryRemovedListener;
@@ -205,12 +203,20 @@ public class HazelcastSessionRepository implements
this.sessions.set(session.getId(), session.getDelegate(),
session.getMaxInactiveIntervalInSeconds(), TimeUnit.SECONDS);
}
else if (session.changed) {
this.sessions.executeOnKey(session.getId(),
new SessionUpdateEntryProcessor(session.getLastAccessedTime(),
session.getMaxInactiveIntervalInSeconds(), session.delta));
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.clearFlags();
session.clearChangeFlags();
}
public HazelcastSession getSession(String id) {
@@ -279,7 +285,11 @@ public class HazelcastSessionRepository implements
private boolean isNew;
private boolean changed;
private boolean sessionIdChanged;
private boolean lastAccessedTimeChanged;
private boolean maxInactiveIntervalChanged;
private Map<String, Object> delta = new HashMap<String, Object>();
@@ -305,7 +315,7 @@ public class HazelcastSessionRepository implements
public void setLastAccessedTime(long lastAccessedTime) {
this.delegate.setLastAccessedTime(lastAccessedTime);
this.changed = true;
this.lastAccessedTimeChanged = true;
flushImmediateIfNecessary();
}
@@ -327,7 +337,7 @@ public class HazelcastSessionRepository implements
public void setMaxInactiveIntervalInSeconds(int interval) {
this.delegate.setMaxInactiveIntervalInSeconds(interval);
this.changed = true;
this.maxInactiveIntervalChanged = true;
flushImmediateIfNecessary();
}
@@ -346,14 +356,12 @@ public class HazelcastSessionRepository implements
public void setAttribute(String attributeName, Object attributeValue) {
this.delegate.setAttribute(attributeName, attributeValue);
this.delta.put(attributeName, attributeValue);
this.changed = true;
flushImmediateIfNecessary();
}
public void removeAttribute(String attributeName) {
this.delegate.removeAttribute(attributeName);
this.delta.put(attributeName, null);
this.changed = true;
flushImmediateIfNecessary();
}
@@ -361,9 +369,16 @@ public class HazelcastSessionRepository implements
return this.delegate;
}
void clearFlags() {
boolean hasChanges() {
return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged
|| !this.delta.isEmpty());
}
void clearChangeFlags() {
this.isNew = false;
this.changed = false;
this.lastAccessedTimeChanged = false;
this.sessionIdChanged = false;
this.maxInactiveIntervalChanged = false;
this.delta.clear();
}
@@ -375,44 +390,4 @@ public class HazelcastSessionRepository implements
}
/**
* Hazelcast {@link EntryProcessor} responsible for handling updates to session.
*
* @since 1.3.2
* @see #save(HazelcastSession)
*/
private static final class SessionUpdateEntryProcessor
extends AbstractEntryProcessor<String, MapSession> {
private final long lastAccessedTime;
private final int maxInactiveIntervalInSeconds;
private final Map<String, Object> delta;
SessionUpdateEntryProcessor(long lastAccessedTime,
int maxInactiveIntervalInSeconds, Map<String, Object> delta) {
this.lastAccessedTime = lastAccessedTime;
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
this.delta = delta;
}
public Object process(Map.Entry<String, MapSession> entry) {
MapSession value = entry.getValue();
value.setLastAccessedTime(this.lastAccessedTime);
value.setMaxInactiveIntervalInSeconds(this.maxInactiveIntervalInSeconds);
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 value;
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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)
*/
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

@@ -143,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 " +
@@ -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());
}
});

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

@@ -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

@@ -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;
@@ -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>
@@ -397,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.
*
@@ -417,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

@@ -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

@@ -805,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

@@ -339,4 +339,17 @@ public class HazelcastSessionRepositoryTests {
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

@@ -525,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

@@ -1175,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