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}