diff --git a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java index 9e8f893479..d639b78ac1 100644 --- a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java +++ b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java @@ -40,8 +40,8 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { public BeanDefinition parse(Element element, ParserContext pc) { if (!namespaceMatchesVersion(element)) { - pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd schema with Spring Security 3.0." + - " Please update your schema declarations to the 3.0 schema.", element); + pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd schema with Spring Security 3." + + " Please update your schema declarations to the 3.1 schema.", element); } String name = pc.getDelegate().getLocalName(element); BeanDefinitionParser parser = parsers.get(name); @@ -131,7 +131,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { private boolean matchesVersionInternal(Element element) { String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); - return schemaLocation.matches("(?m).*spring-security-3.0.xsd.*") + return schemaLocation.matches("(?m).*spring-security-3.*.xsd.*") || schemaLocation.matches("(?m).*spring-security.xsd.*") || !schemaLocation.matches("(?m).*spring-security.*"); } diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index 866d47aed2..71cb8c9ce7 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -24,7 +24,6 @@ import org.springframework.security.authentication.AnonymousAuthenticationProvid import org.springframework.security.authentication.RememberMeAuthenticationProvider; import org.springframework.security.config.Elements; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; -import org.springframework.security.web.PortResolverImpl; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; @@ -33,9 +32,8 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -66,14 +64,11 @@ final class AuthenticationConfigBuilder { private static final String ATT_USER_SERVICE_REF = "user-service-ref"; - private static final String ATT_REF = "ref"; - private Element httpElt; private ParserContext pc; private final boolean autoConfig; private final boolean allowSessionCreation; - private final String portMapperName; private RootBeanDefinition anonymousFilter; private BeanReference anonymousProviderRef; @@ -101,19 +96,32 @@ final class AuthenticationConfigBuilder { final SecureRandom random; - public AuthenticationConfigBuilder(Element element, ParserContext pc, boolean allowSessionCreation, - String portMapperName) { + public AuthenticationConfigBuilder(Element element, ParserContext pc, SessionCreationPolicy sessionPolicy, + BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy) { this.httpElt = element; this.pc = pc; - this.portMapperName = portMapperName; + this.requestCache = requestCache; autoConfig = "true".equals(element.getAttribute(ATT_AUTO_CONFIG)); - this.allowSessionCreation = allowSessionCreation; + this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.never + && sessionPolicy != SessionCreationPolicy.stateless; try { random = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { // Shouldn't happen... throw new RuntimeException("Failed find SHA1PRNG algorithm!"); } + + createAnonymousFilter(); + createRememberMeFilter(authenticationManager); + createBasicFilter(authenticationManager); + createFormLoginFilter(sessionStrategy, authenticationManager); + createOpenIDLoginFilter(sessionStrategy, authenticationManager); + createX509Filter(authenticationManager); + createLogoutFilter(); + createLoginPageFilterIfNeeded(); + createUserServiceInjector(); + createExceptionTranslationFilter(); + } void createRememberMeFilter(BeanReference authenticationManager) { @@ -166,7 +174,6 @@ final class AuthenticationConfigBuilder { formFilter.getPropertyValues().addPropertyValue("allowSessionCreation", new Boolean(allowSessionCreation)); formFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager); - // Id is required by login page filter formFilterId = pc.getReaderContext().generateBeanName(formFilter); pc.registerBeanComponent(new BeanComponentDefinition(formFilter, formFilterId)); @@ -323,7 +330,6 @@ final class AuthenticationConfigBuilder { x509ProviderRef = new RuntimeBeanReference(x509ProviderId); } - void createLoginPageFilterIfNeeded() { boolean needLoginPage = formFilter != null || openIDFilter != null; String formLoginPage = getLoginFormUrl(formEntryPoint); @@ -414,28 +420,6 @@ final class AuthenticationConfigBuilder { etf = etfBuilder.getBeanDefinition(); } - void createRequestCache() { - Element requestCacheElt = DomUtils.getChildElementByTagName(httpElt, Elements.REQUEST_CACHE); - - if (requestCacheElt != null) { - requestCache = new RuntimeBeanReference(requestCacheElt.getAttribute(ATT_REF)); - return; - } - - BeanDefinitionBuilder requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionRequestCache.class); - BeanDefinitionBuilder portResolver = BeanDefinitionBuilder.rootBeanDefinition(PortResolverImpl.class); - portResolver.addPropertyReference("portMapper", portMapperName); - requestCacheBldr.addPropertyValue("createSessionAllowed", allowSessionCreation); - requestCacheBldr.addPropertyValue("portResolver", portResolver.getBeanDefinition()); - - BeanDefinition bean = requestCacheBldr.getBeanDefinition(); - String id = pc.getReaderContext().generateBeanName(bean); - pc.registerBeanComponent(new BeanComponentDefinition(bean, id)); - - this.requestCache = new RuntimeBeanReference(id); - } - - private BeanMetadataElement createAccessDeniedHandler(Element element, ParserContext pc) { String accessDeniedPage = element.getAttribute(ATT_ACCESS_DENIED_PAGE); WebConfigUtils.validateHttpRedirect(accessDeniedPage, pc, pc.extractSource(element)); @@ -610,8 +594,4 @@ final class AuthenticationConfigBuilder { return providers; } - public BeanReference getRequestCache() { - return requestCache; - } - } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index d60f75671e..4b506aafa5 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -1,7 +1,7 @@ package org.springframework.security.config.http; -import static org.springframework.security.config.http.SecurityFilters.*; import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.*; +import static org.springframework.security.config.http.SecurityFilters.*; import java.util.ArrayList; import java.util.Collections; @@ -24,6 +24,7 @@ import org.springframework.security.access.vote.AuthenticatedVoter; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.config.Elements; import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.web.PortResolverImpl; import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator; import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl; import org.springframework.security.web.access.channel.ChannelProcessingFilter; @@ -39,7 +40,11 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa import org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy; import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.NullSecurityContextRepository; import org.springframework.security.web.context.SecurityContextPersistenceFilter; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.NullRequestCache; +import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.SessionManagementFilter; @@ -57,9 +62,6 @@ import org.w3c.dom.Element; */ class HttpConfigurationBuilder { private static final String ATT_CREATE_SESSION = "create-session"; - private static final String OPT_CREATE_SESSION_NEVER = "never"; - private static final String DEF_CREATE_SESSION_IF_REQUIRED = "ifRequired"; - private static final String OPT_CREATE_SESSION_ALWAYS = "always"; private static final String ATT_SESSION_FIXATION_PROTECTION = "session-fixation-protection"; private static final String OPT_SESSION_FIXATION_NO_PROTECTION = "none"; @@ -75,11 +77,13 @@ class HttpConfigurationBuilder { private static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; private static final String ATT_ONCE_PER_REQUEST = "once-per-request"; + private static final String ATT_REF = "ref"; + private final Element httpElt; private final ParserContext pc; private final UrlMatcher matcher; private final Boolean convertPathsToLowerCase; - private final boolean allowSessionCreation; + private final SessionCreationPolicy sessionPolicy; private final List interceptUrls; // Use ManagedMap to allow placeholder resolution @@ -90,13 +94,16 @@ class HttpConfigurationBuilder { private BeanReference contextRepoRef; private BeanReference sessionRegistryRef; private BeanDefinition concurrentSessionFilter; + private BeanDefinition requestCacheAwareFilter; private BeanReference sessionStrategyRef; private RootBeanDefinition sfpf; private BeanDefinition servApiFilter; private String portMapperName; private BeanReference fsi; + private BeanReference requestCache; - public HttpConfigurationBuilder(Element element, ParserContext pc, UrlMatcher matcher, String portMapperName) { + public HttpConfigurationBuilder(Element element, ParserContext pc, UrlMatcher matcher, + String portMapperName, BeanReference authenticationManager) { this.httpElt = element; this.pc = pc; this.portMapperName = portMapperName; @@ -105,10 +112,24 @@ class HttpConfigurationBuilder { // true if Ant path and using lower case convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl(); interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL); - allowSessionCreation = !OPT_CREATE_SESSION_NEVER.equals(element.getAttribute(ATT_CREATE_SESSION)); + String createSession = element.getAttribute(ATT_CREATE_SESSION); + + if (StringUtils.hasText(createSession)) { + sessionPolicy = SessionCreationPolicy.valueOf(createSession); + } else { + sessionPolicy = SessionCreationPolicy.ifRequired; + } + + parseInterceptUrlsForEmptyFilterChains(); + createSecurityContextPersistenceFilter(); + createSessionManagementFilters(); + createRequestCacheFilter(); + createServletApiFilter(); + createChannelProcessingFilter(); + createFilterSecurityInterceptor(authenticationManager); } - void parseInterceptUrlsForEmptyFilterChains() { + private void parseInterceptUrlsForEmptyFilterChains() { filterChainMap = new ManagedMap>(); for (Element urlElt : interceptUrls) { @@ -142,43 +163,44 @@ class HttpConfigurationBuilder { return lowerCase ? path.toLowerCase() : path; } - void createSecurityContextPersistenceFilter() { + private void createSecurityContextPersistenceFilter() { BeanDefinitionBuilder scpf = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextPersistenceFilter.class); String repoRef = httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY); - String createSession = httpElt.getAttribute(ATT_CREATE_SESSION); String disableUrlRewriting = httpElt.getAttribute(ATT_DISABLE_URL_REWRITING); if (StringUtils.hasText(repoRef)) { - if (OPT_CREATE_SESSION_ALWAYS.equals(createSession)) { + if (sessionPolicy == SessionCreationPolicy.always) { scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE); - } else if (StringUtils.hasText(createSession)) { - pc.getReaderContext().error("If using security-context-repository-ref, the only value you can set for " + - "'create-session' is 'always'. Other session creation logic should be handled by the " + - "SecurityContextRepository", httpElt); } } else { - BeanDefinitionBuilder contextRepo = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionSecurityContextRepository.class); - if (OPT_CREATE_SESSION_ALWAYS.equals(createSession)) { - contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE); - scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE); - } else if (OPT_CREATE_SESSION_NEVER.equals(createSession)) { - contextRepo.addPropertyValue("allowSessionCreation", Boolean.FALSE); - scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE); + BeanDefinitionBuilder contextRepo; + if (sessionPolicy == SessionCreationPolicy.stateless) { + contextRepo = BeanDefinitionBuilder.rootBeanDefinition(NullSecurityContextRepository.class); } else { - createSession = DEF_CREATE_SESSION_IF_REQUIRED; - contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE); - scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE); - } + contextRepo = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionSecurityContextRepository.class); + switch (sessionPolicy) { + case always: + contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE); + scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE); + break; + case never: + contextRepo.addPropertyValue("allowSessionCreation", Boolean.FALSE); + scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE); + break; + default: + contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE); + scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE); + } - if ("true".equals(disableUrlRewriting)) { - contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE); + if ("true".equals(disableUrlRewriting)) { + contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE); + } } BeanDefinition repoBean = contextRepo.getBeanDefinition(); repoRef = pc.getReaderContext().generateBeanName(repoBean); pc.registerBeanComponent(new BeanComponentDefinition(repoBean, repoRef)); - } contextRepoRef = new RuntimeBeanReference(repoRef); @@ -187,7 +209,7 @@ class HttpConfigurationBuilder { securityContextPersistenceFilter = scpf.getBeanDefinition(); } - void createSessionManagementFilters() { + private void createSessionManagementFilters() { Element sessionMgmtElt = DomUtils.getChildElementByTagName(httpElt, Elements.SESSION_MANAGEMENT); Element sessionCtrlElt = null; @@ -197,6 +219,11 @@ class HttpConfigurationBuilder { String errorUrl = null; if (sessionMgmtElt != null) { + if (sessionPolicy == SessionCreationPolicy.stateless) { + pc.getReaderContext().error(Elements.SESSION_MANAGEMENT + " cannot be used" + + " in combination with " + ATT_CREATE_SESSION + "='"+ SessionCreationPolicy.stateless +"'", + pc.extractSource(sessionMgmtElt)); + } sessionFixationAttribute = sessionMgmtElt.getAttribute(ATT_SESSION_FIXATION_PROTECTION); invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL); sessionAuthStratRef = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_STRATEGY_REF); @@ -216,7 +243,12 @@ class HttpConfigurationBuilder { sessionFixationAttribute = OPT_SESSION_FIXATION_MIGRATE_SESSION; } else if (StringUtils.hasText(sessionAuthStratRef)) { pc.getReaderContext().error(ATT_SESSION_FIXATION_PROTECTION + " attribute cannot be used" + - " in combination with " + ATT_SESSION_AUTH_STRATEGY_REF, pc.extractSource(sessionCtrlElt)); + " in combination with " + ATT_SESSION_AUTH_STRATEGY_REF, pc.extractSource(sessionMgmtElt)); + } + + if (sessionPolicy == SessionCreationPolicy.stateless) { + // SEC-1424: do nothing + return; } boolean sessionFixationProtectionRequired = !sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION); @@ -323,7 +355,7 @@ class HttpConfigurationBuilder { } // Adds the servlet-api integration filter if required - void createServletApiFilter() { + private void createServletApiFilter() { final String ATT_SERVLET_API_PROVISION = "servlet-api-provision"; final String DEF_SERVLET_API_PROVISION = "true"; @@ -337,7 +369,7 @@ class HttpConfigurationBuilder { } } - void createChannelProcessingFilter() { + private void createChannelProcessingFilter() { ManagedMap channelRequestMap = parseInterceptUrlsForChannelSecurity(); if (channelRequestMap.isEmpty()) { @@ -407,7 +439,36 @@ class HttpConfigurationBuilder { return channelRequestMap; } - void createFilterSecurityInterceptor(BeanReference authManager) { + private void createRequestCacheFilter() { + Element requestCacheElt = DomUtils.getChildElementByTagName(httpElt, Elements.REQUEST_CACHE); + + if (requestCacheElt != null) { + requestCache = new RuntimeBeanReference(requestCacheElt.getAttribute(ATT_REF)); + } else { + BeanDefinitionBuilder requestCacheBldr; + + if (sessionPolicy == SessionCreationPolicy.stateless) { + requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(NullRequestCache.class); + } else { + requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionRequestCache.class); + BeanDefinitionBuilder portResolver = BeanDefinitionBuilder.rootBeanDefinition(PortResolverImpl.class); + portResolver.addPropertyReference("portMapper", portMapperName); + requestCacheBldr.addPropertyValue("createSessionAllowed", sessionPolicy == SessionCreationPolicy.ifRequired); + requestCacheBldr.addPropertyValue("portResolver", portResolver.getBeanDefinition()); + } + + BeanDefinition bean = requestCacheBldr.getBeanDefinition(); + String id = pc.getReaderContext().generateBeanName(bean); + pc.registerBeanComponent(new BeanComponentDefinition(bean, id)); + + this.requestCache = new RuntimeBeanReference(id); + } + + requestCacheAwareFilter = new RootBeanDefinition(RequestCacheAwareFilter.class); + requestCacheAwareFilter.getPropertyValues().addPropertyValue("requestCache", requestCache); + } + + private void createFilterSecurityInterceptor(BeanReference authManager) { boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt); BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc); @@ -459,12 +520,15 @@ class HttpConfigurationBuilder { return sessionStrategyRef; } - - boolean isAllowSessionCreation() { - return allowSessionCreation; + SessionCreationPolicy getSessionCreationPolicy() { + return sessionPolicy; } - public ManagedMap> getFilterChainMap() { + BeanReference getRequestCache() { + return requestCache; + } + + ManagedMap> getFilterChainMap() { return filterChainMap; } @@ -491,6 +555,10 @@ class HttpConfigurationBuilder { filters.add(new OrderDecorator(fsi, FILTER_SECURITY_INTERCEPTOR)); + if (sessionPolicy != SessionCreationPolicy.stateless) { + filters.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER)); + } + return filters; } } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java index 1cec9f698b..9df15b18d9 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java @@ -1,7 +1,5 @@ package org.springframework.security.config.http; -import static org.springframework.security.config.http.SecurityFilters.REQUEST_CACHE_FILTER; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -28,7 +26,6 @@ import org.springframework.security.config.BeanIds; import org.springframework.security.config.Elements; import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean; import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.util.AntUrlPathMatcher; import org.springframework.security.web.util.RegexUrlPathMatcher; import org.springframework.security.web.util.UrlMatcher; @@ -85,33 +82,15 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { final String portMapperName = createPortMapper(element, pc); final UrlMatcher matcher = createUrlMatcher(element); - HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcher, portMapperName); - - httpBldr.parseInterceptUrlsForEmptyFilterChains(); - httpBldr.createSecurityContextPersistenceFilter(); - httpBldr.createSessionManagementFilters(); - ManagedList authenticationProviders = new ManagedList(); BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null); - httpBldr.createServletApiFilter(); - httpBldr.createChannelProcessingFilter(); - httpBldr.createFilterSecurityInterceptor(authenticationManager); + HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcher, + portMapperName, authenticationManager); AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc, - httpBldr.isAllowSessionCreation(), portMapperName); - - authBldr.createAnonymousFilter(); - authBldr.createRememberMeFilter(authenticationManager); - authBldr.createRequestCache(); - authBldr.createBasicFilter(authenticationManager); - authBldr.createFormLoginFilter(httpBldr.getSessionStrategy(), authenticationManager); - authBldr.createOpenIDLoginFilter(httpBldr.getSessionStrategy(), authenticationManager); - authBldr.createX509Filter(authenticationManager); - authBldr.createLogoutFilter(); - authBldr.createLoginPageFilterIfNeeded(); - authBldr.createUserServiceInjector(); - authBldr.createExceptionTranslationFilter(); + httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager, + httpBldr.getSessionStrategy()); List unorderedFilterChain = new ArrayList(); @@ -120,10 +99,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { authenticationProviders.addAll(authBldr.getProviders()); - BeanDefinition requestCacheAwareFilter = new RootBeanDefinition(RequestCacheAwareFilter.class); - requestCacheAwareFilter.getPropertyValues().addPropertyValue("requestCache", authBldr.getRequestCache()); - unorderedFilterChain.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER)); - unorderedFilterChain.addAll(buildCustomFilterList(element, pc)); Collections.sort(unorderedFilterChain, new OrderComparator()); diff --git a/config/src/main/java/org/springframework/security/config/http/SessionCreationPolicy.java b/config/src/main/java/org/springframework/security/config/http/SessionCreationPolicy.java new file mode 100644 index 0000000000..b14b78a060 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/http/SessionCreationPolicy.java @@ -0,0 +1,13 @@ +package org.springframework.security.config.http; + +/** + * + * @author Luke Taylor + * @since 3.1 + */ +enum SessionCreationPolicy { + always, + never, + ifRequired, + stateless +} diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc index ccd41e4a4f..9f1923cf07 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc @@ -258,8 +258,8 @@ http.attlist &= http.attlist &= use-expressions? http.attlist &= - ## Controls the eagerness with which an HTTP session is created. If not set, defaults to "ifRequired". Note that if a custom SecurityContextRepository is set using security-context-repository-ref, then the only value which can be set is "always". Otherwise the session creation behaviour will be determined by the repository bean implementation. - attribute create-session {"ifRequired" | "always" | "never" }? + ## Controls the eagerness with which an HTTP session is created by Spring Security classes. If not set, defaults to "ifRequired". If "stateless" is used, this implies that the application guarantees that it will not create a session. This differs from the use of "never" which mans that Spring Security will not create a session, but will make use of one if the application does. + attribute create-session {"ifRequired" | "always" | "never" | "stateless"}? http.attlist &= ## A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests. attribute security-context-repository-ref {xsd:token}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd index df13cb7fa9..de2d6e750d 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd @@ -671,18 +671,19 @@ - Enables the use of expressions in the 'access' attributes in <intercept-url> elements rather than the traditional list of configuration attributes. Defaults to 'false'. If enabled, each attribute should contain a single boolean expression. If the expression evaluates to 'true', access will be granted. + Enables the use of expressions in the 'access' attributes in <intercept-url> elements rather than the traditional list of configuration attributes. Defaults to 'false'. If enabled, each attribute should contain a single boolean expression. If the expression evaluates to 'true', access will be granted. - Controls the eagerness with which an HTTP session is created. If not set, defaults to "ifRequired". Note that if a custom SecurityContextRepository is set using security-context-repository-ref, then the only value which can be set is "always". Otherwise the session creation behaviour will be determined by the repository bean implementation. + Controls the eagerness with which an HTTP session is created by Spring Security classes. If not set, defaults to "ifRequired". If "stateless" is used, this implies that the application guarantees that it will not create a session. This differs from the use of "never" which mans that Spring Security will not create a session, but will make use of one if the application does. + diff --git a/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java index 8cfbc9e591..fa526f8dd9 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParserTests.java @@ -78,6 +78,7 @@ import org.springframework.security.web.authentication.rememberme.TokenBasedReme import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.NullSecurityContextRepository; import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper; import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; @@ -958,6 +959,23 @@ public class HttpSecurityBeanDefinitionParserTests { assertNull(request.getSession(false)); } + @Test + public void settingCreateSessionToStatelessSetsFilterPropertiesCorrectly() throws Exception { + setContext("" + AUTH_PROVIDER_XML); + SecurityContextPersistenceFilter filter = getFilter(SecurityContextPersistenceFilter.class); + assertEquals(Boolean.FALSE, FieldUtils.getFieldValue(filter, "forceEagerSessionCreation")); + assertTrue(FieldUtils.getFieldValue(filter, "repo") instanceof NullSecurityContextRepository); + assertNull("Session management filter should not be in stack", getFilter(SessionManagementFilter.class)); + assertNull("Request cache filter should not be in stack", getFilter(RequestCacheAwareFilter.class)); + + // Check that an invocation doesn't create a session + FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServletPath("/anything"); + fcp.doFilter(request, new MockHttpServletResponse(), new MockFilterChain()); + assertNull(request.getSession(false)); + } + @Test public void settingCreateSessionToIfRequiredDoesntCreateASessionForPublicInvocation() throws Exception { setContext("" + AUTH_PROVIDER_XML); @@ -1002,15 +1020,6 @@ public class HttpSecurityBeanDefinitionParserTests { assertTrue((Boolean)FieldUtils.getFieldValue(filter, "forceEagerSessionCreation")); } - @Test(expected=BeanDefinitionParsingException.class) - public void cantUseUnsupportedSessionCreationAttributeWithExternallyDefinedSecurityContextRepository() throws Exception { - setContext( - "" + - "" + - " " + - "" + AUTH_PROVIDER_XML); - } - @Test public void expressionBasedAccessAllowsAndDeniesAccessAsExpected() throws Exception { setContext( @@ -1147,11 +1156,7 @@ public class HttpSecurityBeanDefinitionParserTests { ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class); LoginUrlAuthenticationEntryPoint ap = (LoginUrlAuthenticationEntryPoint) etf.getAuthenticationEntryPoint(); assertEquals("/form_login_page", ap.getLoginFormUrl()); - try { - getFilter(DefaultLoginPageGeneratingFilter.class); - fail("Login page generating filter shouldn't be present"); - } catch (Exception expected) { - } + assertNull(getFilter(DefaultLoginPageGeneratingFilter.class)); } @Test @@ -1250,7 +1255,7 @@ public class HttpSecurityBeanDefinitionParserTests { } } - throw new Exception("Filter not found"); + return null; } private RememberMeServices getRememberMeServices() throws Exception { diff --git a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java index 7b5beef2ce..95f550f002 100644 --- a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java +++ b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java @@ -22,11 +22,11 @@ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext Resource inMemoryXml; public InMemoryXmlApplicationContext(String xml) { - this(xml, "3.0", null); + this(xml, "3.1", null); } public InMemoryXmlApplicationContext(String xml, ApplicationContext parent) { - this(xml, "3.0", parent); + this(xml, "3.1", parent); } public InMemoryXmlApplicationContext(String xml, String secVersion, ApplicationContext parent) { diff --git a/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java new file mode 100644 index 0000000000..c36e9d9944 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java @@ -0,0 +1,26 @@ +package org.springframework.security.web.context; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * @author Luke Taylor + * @since 3.1 + */ +public final class NullSecurityContextRepository implements SecurityContextRepository { + + public boolean containsContext(HttpServletRequest request) { + return false; + } + + public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { + return SecurityContextHolder.createEmptyContext(); + } + + public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { + } + +}