/* * Copyright 2014-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.session.web.context; import java.util.Arrays; import java.util.EnumSet; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterRegistration.Dynamic; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.springframework.context.ApplicationContext; import org.springframework.core.Conventions; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.util.Assert; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.AbstractContextLoaderInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.filter.DelegatingFilterProxy; /** * Registers the {@link DelegatingFilterProxy} to use the springSessionRepositoryFilter * before any other registered {@link Filter}. When used with * {@link #AbstractHttpSessionApplicationInitializer(Class...)}, it will also register a * {@link ContextLoaderListener}. When used with * {@link #AbstractHttpSessionApplicationInitializer()}, this class is typically used in * addition to a subclass of {@link AbstractContextLoaderInitializer}. * * <p> * By default the {@link DelegatingFilterProxy} is registered with support for * asynchronous requests, but can be enabled by overriding * {@link #isAsyncSessionSupported()} and {@link #getSessionDispatcherTypes()}. * </p> * * <p> * Additional configuration before and after the springSecurityFilterChain can be added by * overriding {@link #afterSessionRepositoryFilter(ServletContext)}. * </p> * * * <h2>Caveats</h2> * <p> * Subclasses of {@code AbstractDispatcherServletInitializer} will register their filters * before any other {@link Filter}. This means that you will typically want to ensure * subclasses of {@code AbstractDispatcherServletInitializer} are invoked first. This can * be done by ensuring the {@link Order} or {@link Ordered} of * {@code AbstractDispatcherServletInitializer} are sooner than subclasses of * {@code AbstractSecurityWebApplicationInitializer}. * </p> * * @author Rob Winch * */ @Order(100) public abstract class AbstractHttpSessionApplicationInitializer implements WebApplicationInitializer { private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT."; /** * The default name for Spring Session's repository filter. */ public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter"; private final Class<?>[] configurationClasses; /** * Creates a new instance that assumes the Spring Session configuration is loaded by * some other means than this class. For example, a user might create a * {@link ContextLoaderListener} using a subclass of * {@link AbstractContextLoaderInitializer}. * * @see ContextLoaderListener */ protected AbstractHttpSessionApplicationInitializer() { this.configurationClasses = null; } /** * Creates a new instance that will instantiate the {@link ContextLoaderListener} with * the specified classes. * * @param configurationClasses {@code @Configuration} classes that will be used to * configure the context */ protected AbstractHttpSessionApplicationInitializer( Class<?>... configurationClasses) { this.configurationClasses = configurationClasses; } public void onStartup(ServletContext servletContext) throws ServletException { beforeSessionRepositoryFilter(servletContext); if (this.configurationClasses != null) { AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(this.configurationClasses); servletContext.addListener(new ContextLoaderListener(rootAppContext)); } insertSessionRepositoryFilter(servletContext); afterSessionRepositoryFilter(servletContext); } /** * Registers the springSessionRepositoryFilter. * @param servletContext the {@link ServletContext} */ private void insertSessionRepositoryFilter(ServletContext servletContext) { String filterName = DEFAULT_FILTER_NAME; DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy( filterName); String contextAttribute = getWebApplicationContextAttribute(); if (contextAttribute != null) { springSessionRepositoryFilter.setContextAttribute(contextAttribute); } registerFilter(servletContext, true, filterName, springSessionRepositoryFilter); } /** * Inserts the provided {@link Filter}s before existing {@link Filter}s using default * generated names, {@link #getSessionDispatcherTypes()}, and * {@link #isAsyncSessionSupported()}. * * @param servletContext the {@link ServletContext} to use * @param filters the {@link Filter}s to register */ protected final void insertFilters(ServletContext servletContext, Filter... filters) { registerFilters(servletContext, true, filters); } /** * Inserts the provided {@link Filter}s after existing {@link Filter}s using default * generated names, {@link #getSessionDispatcherTypes()}, and * {@link #isAsyncSessionSupported()}. * * @param servletContext the {@link ServletContext} to use * @param filters the {@link Filter}s to register */ protected final void appendFilters(ServletContext servletContext, Filter... filters) { registerFilters(servletContext, false, filters); } /** * Registers the provided {@link Filter}s using default generated names, * {@link #getSessionDispatcherTypes()}, and {@link #isAsyncSessionSupported()}. * * @param servletContext the {@link ServletContext} to use * @param insertBeforeOtherFilters if true, will insert the provided {@link Filter}s * before other {@link Filter}s. Otherwise, will insert the {@link Filter}s after * other {@link Filter}s. * @param filters the {@link Filter}s to register */ private void registerFilters(ServletContext servletContext, boolean insertBeforeOtherFilters, Filter... filters) { Assert.notEmpty(filters, "filters cannot be null or empty"); for (Filter filter : filters) { if (filter == null) { throw new IllegalArgumentException( "filters cannot contain null values. Got " + Arrays.asList(filters)); } String filterName = Conventions.getVariableName(filter); registerFilter(servletContext, insertBeforeOtherFilters, filterName, filter); } } /** * Registers the provided filter using the {@link #isAsyncSessionSupported()} and * {@link #getSessionDispatcherTypes()}. * * @param servletContext the servlet context * @param insertBeforeOtherFilters should this Filter be inserted before or after * other {@link Filter} * @param filterName the filter name * @param filter the filter */ private void registerFilter(ServletContext servletContext, boolean insertBeforeOtherFilters, String filterName, Filter filter) { Dynamic registration = servletContext.addFilter(filterName, filter); if (registration == null) { throw new IllegalStateException( "Duplicate Filter registration for '" + filterName + "'. Check to ensure the Filter is only configured once."); } registration.setAsyncSupported(isAsyncSessionSupported()); EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes(); registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters, "/*"); } /** * Returns the {@link DelegatingFilterProxy#getContextAttribute()} or null if the * parent {@link ApplicationContext} should be used. The default behavior is to use * the parent {@link ApplicationContext}. * * <p> * If {@link #getDispatcherWebApplicationContextSuffix()} is non-null the * {@link WebApplicationContext} for the Dispatcher will be used. This means the child * {@link ApplicationContext} is used to look up the springSessionRepositoryFilter * bean. * </p> * * @return the {@link DelegatingFilterProxy#getContextAttribute()} or null if the * parent {@link ApplicationContext} should be used */ private String getWebApplicationContextAttribute() { String dispatcherServletName = getDispatcherWebApplicationContextSuffix(); if (dispatcherServletName == null) { return null; } return SERVLET_CONTEXT_PREFIX + dispatcherServletName; } /** * Return the {@code <servlet-name>} to use the DispatcherServlet's * {@link WebApplicationContext} to find the {@link DelegatingFilterProxy} or null to * use the parent {@link ApplicationContext}. * * <p> * For example, if you are using AbstractDispatcherServletInitializer or * AbstractAnnotationConfigDispatcherServletInitializer and using the provided Servlet * name, you can return "dispatcher" from this method to use the DispatcherServlet's * {@link WebApplicationContext}. * </p> * * @return the {@code <servlet-name>} of the DispatcherServlet to use its * {@link WebApplicationContext} or null (default) to use the parent * {@link ApplicationContext}. */ protected String getDispatcherWebApplicationContextSuffix() { return null; } /** * Invoked before the springSessionRepositoryFilter is added. * @param servletContext the {@link ServletContext} */ protected void beforeSessionRepositoryFilter(ServletContext servletContext) { } /** * Invoked after the springSessionRepositoryFilter is added. * @param servletContext the {@link ServletContext} */ protected void afterSessionRepositoryFilter(ServletContext servletContext) { } /** * Get the {@link DispatcherType} for the springSessionRepositoryFilter. * @return the {@link DispatcherType} for the filter */ protected EnumSet<DispatcherType> getSessionDispatcherTypes() { return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC); } /** * Determine if the springSessionRepositoryFilter should be marked as supporting * asynch. Default is true. * * @return true if springSessionRepositoryFilter should be marked as supporting asynch */ protected boolean isAsyncSessionSupported() { return true; } }