WebSession supports changeSessionId
Issue: SPR-15571
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -101,6 +101,14 @@ public interface WebSession {
|
||||
*/
|
||||
boolean isStarted();
|
||||
|
||||
/**
|
||||
* Generate a new id for the session and update the underlying session
|
||||
* storage to reflect the new id. After a successful call {@link #getId()}
|
||||
* reflects the new session id.
|
||||
* @return completion notification (success or error)
|
||||
*/
|
||||
Mono<Void> changeSessionId();
|
||||
|
||||
/**
|
||||
* Save the session persisting attributes (e.g. if stored remotely) and also
|
||||
* sending the session id to the client if the session is new.
|
||||
|
||||
@@ -21,11 +21,13 @@ import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.IdGenerator;
|
||||
import org.springframework.web.server.WebSession;
|
||||
|
||||
/**
|
||||
@@ -36,12 +38,16 @@ import org.springframework.web.server.WebSession;
|
||||
*/
|
||||
class DefaultWebSession implements WebSession {
|
||||
|
||||
private final String id;
|
||||
private final AtomicReference<String> id;
|
||||
|
||||
private final IdGenerator idGenerator;
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
private final BiFunction<String, WebSession, Mono<Void>> changeIdOperation;
|
||||
|
||||
private final Function<WebSession, Mono<Void>> saveOperation;
|
||||
|
||||
private final Instant creationTime;
|
||||
@@ -55,14 +61,22 @@ class DefaultWebSession implements WebSession {
|
||||
|
||||
/**
|
||||
* Constructor for creating a brand, new session.
|
||||
* @param id the session id
|
||||
* @param idGenerator the session id generator
|
||||
* @param clock for access to current time
|
||||
*/
|
||||
DefaultWebSession(String id, Clock clock, Function<WebSession, Mono<Void>> saveOperation) {
|
||||
Assert.notNull(id, "'id' is required.");
|
||||
DefaultWebSession(IdGenerator idGenerator, Clock clock,
|
||||
BiFunction<String, WebSession, Mono<Void>> changeIdOperation,
|
||||
Function<WebSession, Mono<Void>> saveOperation) {
|
||||
|
||||
Assert.notNull(idGenerator, "'idGenerator' is required.");
|
||||
Assert.notNull(clock, "'clock' is required.");
|
||||
this.id = id;
|
||||
Assert.notNull(changeIdOperation, "'changeIdOperation' is required.");
|
||||
Assert.notNull(saveOperation, "'saveOperation' is required.");
|
||||
|
||||
this.id = new AtomicReference<>(String.valueOf(idGenerator.generateId()));
|
||||
this.idGenerator = idGenerator;
|
||||
this.clock = clock;
|
||||
this.changeIdOperation = changeIdOperation;
|
||||
this.saveOperation = saveOperation;
|
||||
this.attributes = new ConcurrentHashMap<>();
|
||||
this.creationTime = Instant.now(clock);
|
||||
@@ -81,12 +95,14 @@ class DefaultWebSession implements WebSession {
|
||||
Function<WebSession, Mono<Void>> saveOperation) {
|
||||
|
||||
this.id = existingSession.id;
|
||||
this.idGenerator = existingSession.idGenerator;
|
||||
this.attributes = existingSession.attributes;
|
||||
this.clock = existingSession.clock;
|
||||
this.changeIdOperation = existingSession.changeIdOperation;
|
||||
this.saveOperation = saveOperation;
|
||||
this.creationTime = existingSession.creationTime;
|
||||
this.lastAccessTime = lastAccessTime;
|
||||
this.maxIdleTime = existingSession.maxIdleTime;
|
||||
this.saveOperation = saveOperation;
|
||||
this.state = existingSession.state;
|
||||
}
|
||||
|
||||
@@ -95,19 +111,21 @@ class DefaultWebSession implements WebSession {
|
||||
*/
|
||||
DefaultWebSession(DefaultWebSession existingSession, Instant lastAccessTime) {
|
||||
this.id = existingSession.id;
|
||||
this.idGenerator = existingSession.idGenerator;
|
||||
this.attributes = existingSession.attributes;
|
||||
this.clock = existingSession.clock;
|
||||
this.changeIdOperation = existingSession.changeIdOperation;
|
||||
this.saveOperation = existingSession.saveOperation;
|
||||
this.creationTime = existingSession.creationTime;
|
||||
this.lastAccessTime = lastAccessTime;
|
||||
this.maxIdleTime = existingSession.maxIdleTime;
|
||||
this.saveOperation = existingSession.saveOperation;
|
||||
this.state = existingSession.state;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
return this.id.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,6 +169,14 @@ class DefaultWebSession implements WebSession {
|
||||
return (State.STARTED.equals(value) || (State.NEW.equals(value) && !getAttributes().isEmpty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId() {
|
||||
String oldId = this.id.get();
|
||||
String newId = String.valueOf(this.idGenerator.generateId());
|
||||
this.id.set(newId);
|
||||
return this.changeIdOperation.apply(oldId, this).doOnError(ex -> this.id.set(oldId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save() {
|
||||
return this.saveOperation.apply(this);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,12 +19,13 @@ import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.IdGenerator;
|
||||
import org.springframework.util.JdkIdGenerator;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
|
||||
@@ -39,6 +40,9 @@ import org.springframework.web.server.WebSession;
|
||||
*/
|
||||
public class DefaultWebSessionManager implements WebSessionManager {
|
||||
|
||||
private static final IdGenerator idGenerator = new JdkIdGenerator();
|
||||
|
||||
|
||||
private WebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver();
|
||||
|
||||
private WebSessionStore sessionStore = new InMemoryWebSessionStore();
|
||||
@@ -159,10 +163,10 @@ public class DefaultWebSessionManager implements WebSessionManager {
|
||||
}
|
||||
|
||||
private Mono<DefaultWebSession> createSession(ServerWebExchange exchange) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
String id = UUID.randomUUID().toString();
|
||||
return new DefaultWebSession(id, getClock(), sess -> saveSession(exchange, sess));
|
||||
});
|
||||
return Mono.fromSupplier(() ->
|
||||
new DefaultWebSession(idGenerator, getClock(),
|
||||
(oldId, session) -> this.sessionStore.changeSessionId(oldId, session),
|
||||
session -> saveSession(exchange, session)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,6 +44,13 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
return (this.sessions.containsKey(id) ? Mono.just(this.sessions.get(id)) : Mono.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId(String oldId, WebSession session) {
|
||||
this.sessions.remove(oldId);
|
||||
this.sessions.put(session.getId(), session);
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> removeSession(String id) {
|
||||
this.sessions.remove(id);
|
||||
|
||||
@@ -41,6 +41,18 @@ public interface WebSessionStore {
|
||||
*/
|
||||
Mono<WebSession> retrieveSession(String sessionId);
|
||||
|
||||
/**
|
||||
* Update WebSession data storage to reflect a change in session id.
|
||||
* <p>Note that the same can be achieved via a combination of
|
||||
* {@link #removeSession} + {@link #storeSession}. The purpose of this method
|
||||
* is to allow a more efficient replacement of the session id mapping
|
||||
* without replacing and storing the session with all of its data.
|
||||
* @param oldId the previous session id
|
||||
* @param session the session reflecting the changed session id
|
||||
* @return completion notification (success or error)
|
||||
*/
|
||||
Mono<Void> changeSessionId(String oldId, WebSession session);
|
||||
|
||||
/**
|
||||
* Remove the WebSession for the specified id.
|
||||
* @param sessionId the id of the session to remove
|
||||
|
||||
Reference in New Issue
Block a user