/* * Copyright 2002-2016 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.web.server.session; import java.time.Clock; import java.time.Instant; 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.web.server.ServerWebExchange; import org.springframework.web.server.WebSession; /** * Default implementation of {@link WebSessionManager} with a cookie-based web * session id resolution strategy and simple in-memory session persistence. * * @author Rossen Stoyanchev * @since 5.0 */ public class DefaultWebSessionManager implements WebSessionManager { private WebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver(); private WebSessionStore sessionStore = new InMemoryWebSessionStore(); private Clock clock = Clock.systemDefaultZone(); /** * Configure the session id resolution strategy to use. * <p>By default {@link CookieWebSessionIdResolver} is used. * @param sessionIdResolver the resolver */ public void setSessionIdResolver(WebSessionIdResolver sessionIdResolver) { Assert.notNull(sessionIdResolver, "'sessionIdResolver' is required."); this.sessionIdResolver = sessionIdResolver; } /** * Return the configured {@link WebSessionIdResolver}. */ public WebSessionIdResolver getSessionIdResolver() { return this.sessionIdResolver; } /** * Configure the session persistence strategy to use. * <p>By default {@link InMemoryWebSessionStore} is used. * @param sessionStore the persistence strategy */ public void setSessionStore(WebSessionStore sessionStore) { Assert.notNull(sessionStore, "'sessionStore' is required."); this.sessionStore = sessionStore; } /** * Return the configured {@link WebSessionStore}. */ public WebSessionStore getSessionStore() { return this.sessionStore; } /** * Configure the {@link Clock} for access to current time. During tests you * may use {code Clock.offset(clock, Duration.ofMinutes(-31))} to set the * clock back for example to test changes after sessions expire. * <p>By default {@link Clock#systemDefaultZone()} is used. * @param clock the clock to use */ public void setClock(Clock clock) { Assert.notNull(clock, "'clock' is required."); this.clock = clock; } /** * Return the configured clock for access to current time. */ public Clock getClock() { return this.clock; } @Override public Mono<WebSession> getSession(ServerWebExchange exchange) { return Mono.defer(() -> Flux.fromIterable(getSessionIdResolver().resolveSessionIds(exchange)) .concatMap(this.sessionStore::retrieveSession) .next() .flatMap(session -> validateSession(exchange, session)) .switchIfEmpty(createSession(exchange)) .map(session -> extendSession(exchange, session))); } protected Mono<WebSession> validateSession(ServerWebExchange exchange, WebSession session) { if (session.isExpired()) { this.sessionIdResolver.setSessionId(exchange, ""); return this.sessionStore.removeSession(session.getId()).cast(WebSession.class); } else { return Mono.just(session); } } protected Mono<WebSession> createSession(ServerWebExchange exchange) { String sessionId = UUID.randomUUID().toString(); WebSession session = new DefaultWebSession(sessionId, getClock()); return Mono.just(session); } protected WebSession extendSession(ServerWebExchange exchange, WebSession session) { if (session instanceof ConfigurableWebSession) { ConfigurableWebSession managed = (ConfigurableWebSession) session; managed.setSaveOperation(() -> saveSession(exchange, session)); managed.setLastAccessTime(Instant.now(getClock())); } exchange.getResponse().beforeCommit(session::save); return session; } protected Mono<Void> saveSession(ServerWebExchange exchange, WebSession session) { if (session.isExpired()) { return Mono.error(new IllegalStateException( "Sessions are checked for expiration and have their " + "access time updated when first accessed during request processing. " + "However this session is expired meaning that maxIdleTime elapsed " + "since then and before the call to session.save().")); } if (!session.isStarted()) { return Mono.empty(); } // Force explicit start session.start(); List<String> requestedIds = getSessionIdResolver().resolveSessionIds(exchange); if (requestedIds.isEmpty() || !session.getId().equals(requestedIds.get(0))) { this.sessionIdResolver.setSessionId(exchange, session.getId()); } return this.sessionStore.storeSession(session); } }