parent
6da3518a66
commit
15cc44e6e8
|
@ -109,6 +109,12 @@ public interface WebSession {
|
|||
*/
|
||||
Mono<Void> changeSessionId();
|
||||
|
||||
/**
|
||||
* Invalidate the current session and clear session storage.
|
||||
* @return completion notification (success or error)
|
||||
*/
|
||||
Mono<Void> invalidate();
|
||||
|
||||
/**
|
||||
* Save the session persisting attributes (e.g. if stored remotely) and also
|
||||
* sending the session id to the client if the session is new.
|
||||
|
|
|
@ -90,26 +90,21 @@ public class DefaultWebSessionManager implements WebSessionManager {
|
|||
}
|
||||
|
||||
private Mono<Void> save(ServerWebExchange exchange, WebSession session) {
|
||||
if (session.isExpired()) {
|
||||
return Mono.error(new IllegalStateException("Session='" + session.getId() + "' expired."));
|
||||
}
|
||||
|
||||
if (!session.isStarted()) {
|
||||
if (hasNewSessionId(exchange, session)) {
|
||||
List<String> ids = getSessionIdResolver().resolveSessionIds(exchange);
|
||||
|
||||
if (!session.isStarted() || session.isExpired()) {
|
||||
if (!ids.isEmpty()) {
|
||||
// Expired on retrieve or while processing request, or invalidated..
|
||||
this.sessionIdResolver.expireSession(exchange);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
if (hasNewSessionId(exchange, session)) {
|
||||
if (ids.isEmpty() || !session.getId().equals(ids.get(0))) {
|
||||
this.sessionIdResolver.setSessionId(exchange, session.getId());
|
||||
}
|
||||
|
||||
return session.save();
|
||||
}
|
||||
|
||||
private boolean hasNewSessionId(ServerWebExchange exchange, WebSession session) {
|
||||
List<String> ids = getSessionIdResolver().resolveSessionIds(exchange);
|
||||
return ids.isEmpty() || !session.getId().equals(ids.get(0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,25 +109,22 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
|||
|
||||
private class InMemoryWebSession implements WebSession {
|
||||
|
||||
private final AtomicReference<String> id;
|
||||
private final AtomicReference<String> id = new AtomicReference<>(String.valueOf(idGenerator.generateId()));
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
|
||||
private final Instant creationTime;
|
||||
|
||||
private volatile Instant lastAccessTime;
|
||||
|
||||
private volatile Duration maxIdleTime;
|
||||
private volatile Duration maxIdleTime = Duration.ofMinutes(30);
|
||||
|
||||
private volatile boolean started;
|
||||
private final AtomicReference<State> state = new AtomicReference<>(State.NEW);
|
||||
|
||||
|
||||
InMemoryWebSession() {
|
||||
this.id = new AtomicReference<>(String.valueOf(idGenerator.generateId()));
|
||||
this.attributes = new ConcurrentHashMap<>();
|
||||
this.creationTime = Instant.now(getClock());
|
||||
this.lastAccessTime = this.creationTime;
|
||||
this.maxIdleTime = Duration.ofMinutes(30);
|
||||
}
|
||||
|
||||
|
||||
|
@ -163,12 +160,12 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
|||
|
||||
@Override
|
||||
public void start() {
|
||||
this.started = true;
|
||||
this.state.compareAndSet(State.NEW, State.STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return this.started || !getAttributes().isEmpty();
|
||||
return this.state.get().equals(State.STARTED) || !getAttributes().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -185,21 +182,46 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
|||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> invalidate() {
|
||||
this.state.set(State.EXPIRED);
|
||||
getAttributes().clear();
|
||||
InMemoryWebSessionStore.this.sessions.remove(this.id.get());
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save() {
|
||||
if (!getAttributes().isEmpty()) {
|
||||
this.state.compareAndSet(State.NEW, State.STARTED);
|
||||
}
|
||||
InMemoryWebSessionStore.this.sessions.put(this.getId(), this);
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return (isStarted() && !this.maxIdleTime.isNegative() &&
|
||||
Instant.now(getClock()).minus(this.maxIdleTime).isAfter(this.lastAccessTime));
|
||||
if (this.state.get().equals(State.EXPIRED)) {
|
||||
return true;
|
||||
}
|
||||
if (checkExpired()) {
|
||||
this.state.set(State.EXPIRED);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkExpired() {
|
||||
return isStarted() && !this.maxIdleTime.isNegative() &&
|
||||
Instant.now(getClock()).minus(this.maxIdleTime).isAfter(this.lastAccessTime);
|
||||
}
|
||||
|
||||
private void updateLastAccessTime() {
|
||||
this.lastAccessTime = Instant.now(getClock());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private enum State { NEW, STARTED, EXPIRED }
|
||||
|
||||
}
|
||||
|
|
|
@ -70,7 +70,6 @@ public class DefaultWebSessionManagerTests {
|
|||
@Before
|
||||
public void setUp() throws Exception {
|
||||
when(this.store.createWebSession()).thenReturn(Mono.just(this.createSession));
|
||||
when(this.store.updateLastAccessTime(any())).thenReturn(Mono.just(this.updateSession));
|
||||
when(this.createSession.save()).thenReturn(Mono.empty());
|
||||
when(this.updateSession.getId()).thenReturn("update-session-id");
|
||||
|
||||
|
|
|
@ -89,4 +89,9 @@ public class InMemoryWebSessionStoreTests {
|
|||
Instant time2 = session2.getLastAccessTime();
|
||||
assertTrue(time1.isBefore(time2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidate() throws Exception {
|
||||
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
@ -136,14 +135,13 @@ public class WebSessionIntegrationTests extends AbstractHttpHandlerIntegrationTe
|
|||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
String id = extractSessionId(response.getHeaders());
|
||||
assertNotNull(id);
|
||||
assertEquals(1, this.handler.getSessionRequestCount());
|
||||
|
||||
// Now fast-forward by 31 minutes
|
||||
InMemoryWebSessionStore store = (InMemoryWebSessionStore) this.sessionManager.getSessionStore();
|
||||
store.setClock(Clock.offset(store.getClock(), Duration.ofMinutes(31)));
|
||||
|
||||
// Second request: session expires
|
||||
URI uri = new URI("http://localhost:" + this.port + "/?expiredSession");
|
||||
URI uri = new URI("http://localhost:" + this.port + "/?expire");
|
||||
request = RequestEntity.get(uri).header("Cookie", "SESSION=" + id).build();
|
||||
response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
|
@ -177,6 +175,28 @@ public class WebSessionIntegrationTests extends AbstractHttpHandlerIntegrationTe
|
|||
assertEquals(2, this.handler.getSessionRequestCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidate() throws Exception {
|
||||
|
||||
// First request: no session yet, new session created
|
||||
RequestEntity<Void> request = RequestEntity.get(createUri()).build();
|
||||
ResponseEntity<Void> response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
String id = extractSessionId(response.getHeaders());
|
||||
assertNotNull(id);
|
||||
|
||||
// Second request: invalidates session
|
||||
URI uri = new URI("http://localhost:" + this.port + "/?invalidate");
|
||||
request = RequestEntity.get(uri).header("Cookie", "SESSION=" + id).build();
|
||||
response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
String value = response.getHeaders().getFirst("Set-Cookie");
|
||||
assertNotNull(value);
|
||||
assertTrue("Actual value: " + value, value.contains("Max-Age=0"));
|
||||
}
|
||||
|
||||
private String extractSessionId(HttpHeaders headers) {
|
||||
List<String> headerValues = headers.get("Set-Cookie");
|
||||
assertNotNull(headerValues);
|
||||
|
@ -206,7 +226,7 @@ public class WebSessionIntegrationTests extends AbstractHttpHandlerIntegrationTe
|
|||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
if (exchange.getRequest().getQueryParams().containsKey("expiredSession")) {
|
||||
if (exchange.getRequest().getQueryParams().containsKey("expire")) {
|
||||
return exchange.getSession().doOnNext(session -> {
|
||||
// Don't do anything, leave it expired...
|
||||
}).then();
|
||||
|
@ -215,6 +235,9 @@ public class WebSessionIntegrationTests extends AbstractHttpHandlerIntegrationTe
|
|||
return exchange.getSession().flatMap(session ->
|
||||
session.changeSessionId().doOnSuccess(aVoid -> updateSessionAttribute(session)));
|
||||
}
|
||||
else if (exchange.getRequest().getQueryParams().containsKey("invalidate")) {
|
||||
return exchange.getSession().doOnNext(WebSession::invalidate).then();
|
||||
}
|
||||
else {
|
||||
return exchange.getSession().doOnSuccess(this::updateSessionAttribute).then();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue