001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.session;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.List;
022import java.util.Locale;
023
024import javax.annotation.PostConstruct;
025
026import org.springframework.beans.factory.ObjectProvider;
027import org.springframework.boot.WebApplicationType;
028import org.springframework.boot.autoconfigure.AutoConfigureAfter;
029import org.springframework.boot.autoconfigure.AutoConfigureBefore;
030import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
031import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
032import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
033import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
034import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
035import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
036import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
037import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
038import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration;
039import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
040import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
041import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration;
042import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
043import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
044import org.springframework.boot.autoconfigure.web.ServerProperties;
045import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
046import org.springframework.boot.context.properties.EnableConfigurationProperties;
047import org.springframework.boot.context.properties.PropertyMapper;
048import org.springframework.boot.web.servlet.server.Session.Cookie;
049import org.springframework.context.ApplicationContext;
050import org.springframework.context.annotation.Bean;
051import org.springframework.context.annotation.Conditional;
052import org.springframework.context.annotation.Configuration;
053import org.springframework.context.annotation.Import;
054import org.springframework.context.annotation.ImportSelector;
055import org.springframework.core.type.AnnotationMetadata;
056import org.springframework.session.ReactiveSessionRepository;
057import org.springframework.session.Session;
058import org.springframework.session.SessionRepository;
059import org.springframework.session.web.http.CookieHttpSessionIdResolver;
060import org.springframework.session.web.http.CookieSerializer;
061import org.springframework.session.web.http.DefaultCookieSerializer;
062import org.springframework.session.web.http.HttpSessionIdResolver;
063import org.springframework.util.StringUtils;
064
065/**
066 * {@link EnableAutoConfiguration Auto-configuration} for Spring Session.
067 *
068 * @author Andy Wilkinson
069 * @author Tommy Ludwig
070 * @author EddĂș MelĂ©ndez
071 * @author Stephane Nicoll
072 * @author Vedran Pavic
073 * @since 1.4.0
074 */
075@Configuration
076@ConditionalOnClass(Session.class)
077@ConditionalOnWebApplication
078@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
079@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,
080                JdbcTemplateAutoConfiguration.class, MongoDataAutoConfiguration.class,
081                MongoReactiveDataAutoConfiguration.class, RedisAutoConfiguration.class,
082                RedisReactiveAutoConfiguration.class })
083@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
084public class SessionAutoConfiguration {
085
086        @Configuration
087        @ConditionalOnWebApplication(type = Type.SERVLET)
088        @Import({ ServletSessionRepositoryValidator.class,
089                        SessionRepositoryFilterConfiguration.class })
090        static class ServletSessionConfiguration {
091
092                @Bean
093                @Conditional(DefaultCookieSerializerCondition.class)
094                public DefaultCookieSerializer cookieSerializer(
095                                ServerProperties serverProperties) {
096                        Cookie cookie = serverProperties.getServlet().getSession().getCookie();
097                        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
098                        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
099                        map.from(cookie::getName).to(cookieSerializer::setCookieName);
100                        map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
101                        map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
102                        map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
103                        map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
104                        map.from(cookie::getMaxAge).to((maxAge) -> cookieSerializer
105                                        .setCookieMaxAge((int) maxAge.getSeconds()));
106                        return cookieSerializer;
107                }
108
109                @Configuration
110                @ConditionalOnMissingBean(SessionRepository.class)
111                @Import({ ServletSessionRepositoryImplementationValidator.class,
112                                ServletSessionConfigurationImportSelector.class })
113                static class ServletSessionRepositoryConfiguration {
114
115                }
116
117        }
118
119        @Configuration
120        @ConditionalOnWebApplication(type = Type.REACTIVE)
121        @Import(ReactiveSessionRepositoryValidator.class)
122        static class ReactiveSessionConfiguration {
123
124                @Configuration
125                @ConditionalOnMissingBean(ReactiveSessionRepository.class)
126                @Import({ ReactiveSessionRepositoryImplementationValidator.class,
127                                ReactiveSessionConfigurationImportSelector.class })
128                static class ReactiveSessionRepositoryConfiguration {
129
130                }
131
132        }
133
134        /**
135         * Condition to trigger the creation of a {@link DefaultCookieSerializer}. This kicks
136         * in if either no {@link HttpSessionIdResolver} and {@link CookieSerializer} beans
137         * are registered, or if {@link CookieHttpSessionIdResolver} is registered but
138         * {@link CookieSerializer} is not.
139         */
140        static class DefaultCookieSerializerCondition extends AnyNestedCondition {
141
142                DefaultCookieSerializerCondition() {
143                        super(ConfigurationPhase.REGISTER_BEAN);
144                }
145
146                @ConditionalOnMissingBean({ HttpSessionIdResolver.class, CookieSerializer.class })
147                static class NoComponentsAvailable {
148
149                }
150
151                @ConditionalOnBean(CookieHttpSessionIdResolver.class)
152                @ConditionalOnMissingBean(CookieSerializer.class)
153                static class CookieHttpSessionIdResolverAvailable {
154
155                }
156
157        }
158
159        /**
160         * {@link ImportSelector} base class to add {@link StoreType} configuration classes.
161         */
162        abstract static class SessionConfigurationImportSelector implements ImportSelector {
163
164                protected final String[] selectImports(WebApplicationType webApplicationType) {
165                        List<String> imports = new ArrayList<>();
166                        StoreType[] types = StoreType.values();
167                        for (int i = 0; i < types.length; i++) {
168                                imports.add(SessionStoreMappings.getConfigurationClass(webApplicationType,
169                                                types[i]));
170                        }
171                        return StringUtils.toStringArray(imports);
172                }
173
174        }
175
176        /**
177         * {@link ImportSelector} to add {@link StoreType} configuration classes for reactive
178         * web applications.
179         */
180        static class ReactiveSessionConfigurationImportSelector
181                        extends SessionConfigurationImportSelector {
182
183                @Override
184                public String[] selectImports(AnnotationMetadata importingClassMetadata) {
185                        return super.selectImports(WebApplicationType.REACTIVE);
186                }
187
188        }
189
190        /**
191         * {@link ImportSelector} to add {@link StoreType} configuration classes for Servlet
192         * web applications.
193         */
194        static class ServletSessionConfigurationImportSelector
195                        extends SessionConfigurationImportSelector {
196
197                @Override
198                public String[] selectImports(AnnotationMetadata importingClassMetadata) {
199                        return super.selectImports(WebApplicationType.SERVLET);
200                }
201
202        }
203
204        /**
205         * Base class for beans used to validate that only one supported implementation is
206         * available in the classpath when the store-type property is not set.
207         */
208        abstract static class AbstractSessionRepositoryImplementationValidator {
209
210                private final List<String> candidates;
211
212                private final ClassLoader classLoader;
213
214                private final SessionProperties sessionProperties;
215
216                AbstractSessionRepositoryImplementationValidator(
217                                ApplicationContext applicationContext,
218                                SessionProperties sessionProperties, List<String> candidates) {
219                        this.classLoader = applicationContext.getClassLoader();
220                        this.sessionProperties = sessionProperties;
221                        this.candidates = candidates;
222                }
223
224                @PostConstruct
225                public void checkAvailableImplementations() {
226                        List<Class<?>> availableCandidates = new ArrayList<>();
227                        for (String candidate : this.candidates) {
228                                addCandidateIfAvailable(availableCandidates, candidate);
229                        }
230                        StoreType storeType = this.sessionProperties.getStoreType();
231                        if (availableCandidates.size() > 1 && storeType == null) {
232                                throw new NonUniqueSessionRepositoryException(availableCandidates);
233                        }
234                }
235
236                private void addCandidateIfAvailable(List<Class<?>> candidates, String type) {
237                        try {
238                                Class<?> candidate = this.classLoader.loadClass(type);
239                                if (candidate != null) {
240                                        candidates.add(candidate);
241                                }
242                        }
243                        catch (Throwable ex) {
244                                // Ignore
245                        }
246                }
247
248        }
249
250        /**
251         * Bean used to validate that only one supported implementation is available in the
252         * classpath when the store-type property is not set.
253         */
254        static class ServletSessionRepositoryImplementationValidator
255                        extends AbstractSessionRepositoryImplementationValidator {
256
257                ServletSessionRepositoryImplementationValidator(
258                                ApplicationContext applicationContext,
259                                SessionProperties sessionProperties) {
260                        super(applicationContext, sessionProperties, Arrays.asList(
261                                        "org.springframework.session.hazelcast.HazelcastSessionRepository",
262                                        "org.springframework.session.jdbc.JdbcOperationsSessionRepository",
263                                        "org.springframework.session.data.mongo.MongoOperationsSessionRepository",
264                                        "org.springframework.session.data.redis.RedisOperationsSessionRepository"));
265                }
266
267        }
268
269        /**
270         * Bean used to validate that only one supported implementation is available in the
271         * classpath when the store-type property is not set.
272         */
273        static class ReactiveSessionRepositoryImplementationValidator
274                        extends AbstractSessionRepositoryImplementationValidator {
275
276                ReactiveSessionRepositoryImplementationValidator(
277                                ApplicationContext applicationContext,
278                                SessionProperties sessionProperties) {
279                        super(applicationContext, sessionProperties, Arrays.asList(
280                                        "org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository",
281                                        "org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository"));
282                }
283
284        }
285
286        /**
287         * Base class for validating that a (reactive) session repository bean exists.
288         */
289        abstract static class AbstractSessionRepositoryValidator {
290
291                private final SessionProperties sessionProperties;
292
293                private final ObjectProvider<?> sessionRepositoryProvider;
294
295                protected AbstractSessionRepositoryValidator(SessionProperties sessionProperties,
296                                ObjectProvider<?> sessionRepositoryProvider) {
297                        this.sessionProperties = sessionProperties;
298                        this.sessionRepositoryProvider = sessionRepositoryProvider;
299                }
300
301                @PostConstruct
302                public void checkSessionRepository() {
303                        StoreType storeType = this.sessionProperties.getStoreType();
304                        if (storeType != StoreType.NONE
305                                        && this.sessionRepositoryProvider.getIfAvailable() == null
306                                        && storeType != null) {
307                                throw new SessionRepositoryUnavailableException(
308                                                "No session repository could be auto-configured, check your "
309                                                                + "configuration (session store type is '"
310                                                                + storeType.name().toLowerCase(Locale.ENGLISH) + "')",
311                                                storeType);
312                        }
313                }
314
315        }
316
317        /**
318         * Bean used to validate that a {@link SessionRepository} exists and provide a
319         * meaningful message if that's not the case.
320         */
321        static class ServletSessionRepositoryValidator
322                        extends AbstractSessionRepositoryValidator {
323
324                ServletSessionRepositoryValidator(SessionProperties sessionProperties,
325                                ObjectProvider<SessionRepository<?>> sessionRepositoryProvider) {
326                        super(sessionProperties, sessionRepositoryProvider);
327                }
328
329        }
330
331        /**
332         * Bean used to validate that a {@link ReactiveSessionRepository} exists and provide a
333         * meaningful message if that's not the case.
334         */
335        static class ReactiveSessionRepositoryValidator
336                        extends AbstractSessionRepositoryValidator {
337
338                ReactiveSessionRepositoryValidator(SessionProperties sessionProperties,
339                                ObjectProvider<ReactiveSessionRepository<?>> sessionRepositoryProvider) {
340                        super(sessionProperties, sessionRepositoryProvider);
341                }
342
343        }
344
345}