Fix session event handling in HazelcastSessionRepository
Previously, invoking HttpServletRequest#changeSessionId on session backed by HazelcastSessionRepository generated generated invalid session destroyed and session created events. This was due to use of IMap#remove and IMap#set when handling the change session id. This commit improves change session id handling to prevent publishing invalid events by using IMap#delete instead of IMap#remove and keeping track of originally assigned session id. Closes gh-1077
This commit is contained in:
@@ -122,7 +122,12 @@ public final class MapSession implements Session, Serializable {
|
|||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getOriginalId() {
|
/**
|
||||||
|
* Get the original session id.
|
||||||
|
* @return the original session id
|
||||||
|
* @see #changeSessionId()
|
||||||
|
*/
|
||||||
|
public String getOriginalId() {
|
||||||
return this.originalId;
|
return this.originalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2017 the original author or authors.
|
* Copyright 2014-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -52,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
* expected after each SessionEvent.
|
* expected after each SessionEvent.
|
||||||
*
|
*
|
||||||
* @author Tommy Ludwig
|
* @author Tommy Ludwig
|
||||||
|
* @author Vedran Pavic
|
||||||
*/
|
*/
|
||||||
@RunWith(SpringRunner.class)
|
@RunWith(SpringRunner.class)
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
@@ -168,6 +169,24 @@ public class EnableHazelcastHttpSessionEventsTests<S extends Session> {
|
|||||||
assertThat(this.repository.findById(sessionToUpdate.getId())).isNotNull();
|
assertThat(this.repository.findById(sessionToUpdate.getId())).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // gh-1077
|
||||||
|
public void changeSessionIdNoEventTest() throws InterruptedException {
|
||||||
|
S sessionToSave = this.repository.createSession();
|
||||||
|
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
|
||||||
|
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionCreatedEvent.class);
|
||||||
|
this.registry.clear();
|
||||||
|
|
||||||
|
sessionToSave.changeSessionId();
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||||
static class HazelcastSessionConfig {
|
static class HazelcastSessionConfig {
|
||||||
|
|||||||
@@ -224,20 +224,30 @@ public class HazelcastSessionRepository implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(HazelcastSession session) {
|
public void save(HazelcastSession session) {
|
||||||
if (!session.getId().equals(session.originalId)) {
|
|
||||||
this.sessions.remove(session.originalId);
|
|
||||||
session.originalId = session.getId();
|
|
||||||
}
|
|
||||||
if (session.isNew) {
|
if (session.isNew) {
|
||||||
this.sessions.set(session.getId(), session.getDelegate(),
|
this.sessions.set(session.getId(), session.getDelegate(),
|
||||||
session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
|
session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
else if (session.changed) {
|
else if (session.sessionIdChanged) {
|
||||||
this.sessions.executeOnKey(session.getId(),
|
this.sessions.delete(session.originalId);
|
||||||
new SessionUpdateEntryProcessor(session.getLastAccessedTime(),
|
session.originalId = session.getId();
|
||||||
session.getMaxInactiveInterval(), session.delta));
|
this.sessions.set(session.getId(), session.getDelegate(),
|
||||||
|
session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
session.clearFlags();
|
else if (session.hasChanges()) {
|
||||||
|
SessionUpdateEntryProcessor entryProcessor = new SessionUpdateEntryProcessor();
|
||||||
|
if (session.lastAccessedTimeChanged) {
|
||||||
|
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
|
||||||
|
}
|
||||||
|
if (session.maxInactiveIntervalChanged) {
|
||||||
|
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
|
||||||
|
}
|
||||||
|
if (!session.delta.isEmpty()) {
|
||||||
|
entryProcessor.setDelta(session.delta);
|
||||||
|
}
|
||||||
|
this.sessions.executeOnKey(session.getId(), entryProcessor);
|
||||||
|
}
|
||||||
|
session.clearChangeFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -275,10 +285,13 @@ public class HazelcastSessionRepository implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void entryAdded(EntryEvent<String, MapSession> event) {
|
public void entryAdded(EntryEvent<String, MapSession> event) {
|
||||||
if (logger.isDebugEnabled()) {
|
MapSession session = event.getValue();
|
||||||
logger.debug("Session created with id: " + event.getValue().getId());
|
if (session.getId().equals(session.getOriginalId())) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Session created with id: " + session.getId());
|
||||||
|
}
|
||||||
|
this.eventPublisher.publishEvent(new SessionCreatedEvent(this, session));
|
||||||
}
|
}
|
||||||
this.eventPublisher.publishEvent(new SessionCreatedEvent(this, event.getValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -292,11 +305,13 @@ public class HazelcastSessionRepository implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void entryRemoved(EntryEvent<String, MapSession> event) {
|
public void entryRemoved(EntryEvent<String, MapSession> event) {
|
||||||
if (logger.isDebugEnabled()) {
|
MapSession session = event.getOldValue();
|
||||||
logger.debug("Session deleted with id: " + event.getOldValue().getId());
|
if (session != null) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Session deleted with id: " + session.getId());
|
||||||
|
}
|
||||||
|
this.eventPublisher.publishEvent(new SessionDeletedEvent(this, session));
|
||||||
}
|
}
|
||||||
this.eventPublisher
|
|
||||||
.publishEvent(new SessionDeletedEvent(this, event.getOldValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -311,7 +326,11 @@ public class HazelcastSessionRepository implements
|
|||||||
|
|
||||||
private boolean isNew;
|
private boolean isNew;
|
||||||
|
|
||||||
private boolean changed;
|
private boolean sessionIdChanged;
|
||||||
|
|
||||||
|
private boolean lastAccessedTimeChanged;
|
||||||
|
|
||||||
|
private boolean maxInactiveIntervalChanged;
|
||||||
|
|
||||||
private String originalId;
|
private String originalId;
|
||||||
|
|
||||||
@@ -341,7 +360,7 @@ public class HazelcastSessionRepository implements
|
|||||||
@Override
|
@Override
|
||||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||||
this.delegate.setLastAccessedTime(lastAccessedTime);
|
this.delegate.setLastAccessedTime(lastAccessedTime);
|
||||||
this.changed = true;
|
this.lastAccessedTimeChanged = true;
|
||||||
flushImmediateIfNecessary();
|
flushImmediateIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,8 +381,9 @@ public class HazelcastSessionRepository implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String changeSessionId() {
|
public String changeSessionId() {
|
||||||
this.isNew = true;
|
String newSessionId = this.delegate.changeSessionId();
|
||||||
return this.delegate.changeSessionId();
|
this.sessionIdChanged = true;
|
||||||
|
return newSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -374,7 +394,7 @@ public class HazelcastSessionRepository implements
|
|||||||
@Override
|
@Override
|
||||||
public void setMaxInactiveInterval(Duration interval) {
|
public void setMaxInactiveInterval(Duration interval) {
|
||||||
this.delegate.setMaxInactiveInterval(interval);
|
this.delegate.setMaxInactiveInterval(interval);
|
||||||
this.changed = true;
|
this.maxInactiveIntervalChanged = true;
|
||||||
flushImmediateIfNecessary();
|
flushImmediateIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +417,6 @@ public class HazelcastSessionRepository implements
|
|||||||
public void setAttribute(String attributeName, Object attributeValue) {
|
public void setAttribute(String attributeName, Object attributeValue) {
|
||||||
this.delegate.setAttribute(attributeName, attributeValue);
|
this.delegate.setAttribute(attributeName, attributeValue);
|
||||||
this.delta.put(attributeName, attributeValue);
|
this.delta.put(attributeName, attributeValue);
|
||||||
this.changed = true;
|
|
||||||
flushImmediateIfNecessary();
|
flushImmediateIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +424,6 @@ public class HazelcastSessionRepository implements
|
|||||||
public void removeAttribute(String attributeName) {
|
public void removeAttribute(String attributeName) {
|
||||||
this.delegate.removeAttribute(attributeName);
|
this.delegate.removeAttribute(attributeName);
|
||||||
this.delta.put(attributeName, null);
|
this.delta.put(attributeName, null);
|
||||||
this.changed = true;
|
|
||||||
flushImmediateIfNecessary();
|
flushImmediateIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,9 +431,16 @@ public class HazelcastSessionRepository implements
|
|||||||
return this.delegate;
|
return this.delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearFlags() {
|
boolean hasChanges() {
|
||||||
|
return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged
|
||||||
|
|| !this.delta.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearChangeFlags() {
|
||||||
this.isNew = false;
|
this.isNew = false;
|
||||||
this.changed = false;
|
this.lastAccessedTimeChanged = false;
|
||||||
|
this.sessionIdChanged = false;
|
||||||
|
this.maxInactiveIntervalChanged = false;
|
||||||
this.delta.clear();
|
this.delta.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,18 +461,11 @@ public class HazelcastSessionRepository implements
|
|||||||
private static final class SessionUpdateEntryProcessor
|
private static final class SessionUpdateEntryProcessor
|
||||||
extends AbstractEntryProcessor<String, MapSession> {
|
extends AbstractEntryProcessor<String, MapSession> {
|
||||||
|
|
||||||
private final Instant lastAccessedTime;
|
private Instant lastAccessedTime;
|
||||||
|
|
||||||
private final Duration maxInactiveInterval;
|
private Duration maxInactiveInterval;
|
||||||
|
|
||||||
private final Map<String, Object> delta;
|
private Map<String, Object> delta;
|
||||||
|
|
||||||
SessionUpdateEntryProcessor(Instant lastAccessedTime,
|
|
||||||
Duration maxInactiveInterval, Map<String, Object> delta) {
|
|
||||||
this.lastAccessedTime = lastAccessedTime;
|
|
||||||
this.maxInactiveInterval = maxInactiveInterval;
|
|
||||||
this.delta = delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object process(Map.Entry<String, MapSession> entry) {
|
public Object process(Map.Entry<String, MapSession> entry) {
|
||||||
@@ -455,20 +473,38 @@ public class HazelcastSessionRepository implements
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
value.setLastAccessedTime(this.lastAccessedTime);
|
if (this.lastAccessedTime != null) {
|
||||||
value.setMaxInactiveInterval(this.maxInactiveInterval);
|
value.setLastAccessedTime(this.lastAccessedTime);
|
||||||
for (final Map.Entry<String, Object> attribute : this.delta.entrySet()) {
|
}
|
||||||
if (attribute.getValue() != null) {
|
if (this.maxInactiveInterval != null) {
|
||||||
value.setAttribute(attribute.getKey(), attribute.getValue());
|
value.setMaxInactiveInterval(this.maxInactiveInterval);
|
||||||
}
|
}
|
||||||
else {
|
if (this.delta != null) {
|
||||||
value.removeAttribute(attribute.getKey());
|
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);
|
entry.setValue(value);
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||||
|
this.lastAccessedTime = lastAccessedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxInactiveInterval(Duration maxInactiveInterval) {
|
||||||
|
this.maxInactiveInterval = maxInactiveInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDelta(Map<String, Object> delta) {
|
||||||
|
this.delta = delta;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user