
org.springframework.session.web.server.session.SpringSessionWebSessionStore Maven / Gradle / Ivy
/*
* 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.
* You may obtain a copy of the License at
*
* https://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.web.server.session;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.WebSessionStore;
/**
* The {@link WebSessionStore} implementation that provides the {@link WebSession}
* implementation backed by a {@link Session} returned by the
* {@link ReactiveSessionRepository}.
*
* @param the {@link Session} type
* @author Rob Winch
* @author Vedran Pavic
* @since 2.0
*/
public class SpringSessionWebSessionStore implements WebSessionStore {
private final ReactiveSessionRepository sessions;
private Clock clock = Clock.system(ZoneOffset.UTC);
public SpringSessionWebSessionStore(ReactiveSessionRepository reactiveSessionRepository) {
Assert.notNull(reactiveSessionRepository, "reactiveSessionRepository cannot be null");
this.sessions = reactiveSessionRepository;
}
/**
* Configure the {@link Clock} to use to set lastAccessTime on every created session
* and to calculate if it is expired.
*
* This may be useful to align to different timezone or to set the clock back in a
* test, e.g. {@code Clock.offset(clock, Duration.ofMinutes(-31))} in order to
* simulate session expiration.
*
* By default this is {@code Clock.system(ZoneId.of("GMT"))}.
* @param clock the clock to use
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "clock cannot be null");
this.clock = clock;
}
@Override
public Mono createWebSession() {
return this.sessions.createSession().map(this::createSession);
}
@Override
public Mono updateLastAccessTime(WebSession session) {
@SuppressWarnings("unchecked")
SpringSessionWebSession springSessionWebSession = (SpringSessionWebSession) session;
springSessionWebSession.session.setLastAccessedTime(this.clock.instant());
return Mono.just(session);
}
@Override
public Mono retrieveSession(String sessionId) {
return this.sessions.findById(sessionId)
.doOnNext((session) -> session.setLastAccessedTime(this.clock.instant()))
.map(this::existingSession);
}
@Override
public Mono removeSession(String sessionId) {
return this.sessions.deleteById(sessionId);
}
private SpringSessionWebSession createSession(S session) {
return new SpringSessionWebSession(session, State.NEW);
}
private SpringSessionWebSession existingSession(S session) {
return new SpringSessionWebSession(session, State.STARTED);
}
/**
* Adapts Spring Session's {@link Session} to a {@link WebSession}.
*/
private class SpringSessionWebSession implements WebSession {
private final S session;
private final Map attributes;
private AtomicReference state = new AtomicReference<>();
SpringSessionWebSession(S session, State state) {
Assert.notNull(session, "session cannot be null");
this.session = session;
this.attributes = new SpringSessionMap(session);
this.state.set(state);
}
@Override
public String getId() {
return this.session.getId();
}
@Override
public Mono changeSessionId() {
return Mono.defer(() -> {
this.session.changeSessionId();
return save();
});
}
@Override
public Map getAttributes() {
return this.attributes;
}
@Override
public void start() {
this.state.compareAndSet(State.NEW, State.STARTED);
}
@Override
public boolean isStarted() {
State value = this.state.get();
return (State.STARTED.equals(value) || (State.NEW.equals(value) && !getAttributes().isEmpty()));
}
@Override
public Mono invalidate() {
this.state.set(State.EXPIRED);
return SpringSessionWebSessionStore.this.sessions.deleteById(this.session.getId());
}
@Override
public Mono save() {
return SpringSessionWebSessionStore.this.sessions.save(this.session);
}
@Override
public boolean isExpired() {
if (this.state.get().equals(State.EXPIRED)) {
return true;
}
if (this.session.isExpired()) {
this.state.set(State.EXPIRED);
return true;
}
return false;
}
@Override
public Instant getCreationTime() {
return this.session.getCreationTime();
}
@Override
public Instant getLastAccessTime() {
return this.session.getLastAccessedTime();
}
@Override
public Duration getMaxIdleTime() {
return this.session.getMaxInactiveInterval();
}
@Override
public void setMaxIdleTime(Duration maxIdleTime) {
this.session.setMaxInactiveInterval(maxIdleTime);
}
}
private enum State {
NEW, STARTED, EXPIRED
}
private static class SpringSessionMap implements Map {
private final Session session;
private final Collection