2
0
Эх сурвалжийг харах

SEC-1424: Added support for "stateless" option for create-session attribute, designed for applications which do not use sessions at all.

Luke Taylor 15 жил өмнө
parent
commit
f0466b6488

+ 3 - 3
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.*");
     }

+ 18 - 38
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;
-    }
-
 }

+ 105 - 37
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<Element> 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<BeanDefinition, List<BeanMetadataElement>>();
 
         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<BeanDefinition,BeanDefinition> 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;
     }
 
+    SessionCreationPolicy getSessionCreationPolicy() {
+        return sessionPolicy;
+    }
 
-    boolean isAllowSessionCreation() {
-        return allowSessionCreation;
+    BeanReference getRequestCache() {
+        return requestCache;
     }
 
-    public ManagedMap<BeanDefinition, List<BeanMetadataElement>> getFilterChainMap() {
+    ManagedMap<BeanDefinition, List<BeanMetadataElement>> 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;
     }
 }

+ 4 - 29
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<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
         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<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
 
@@ -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());

+ 13 - 0
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
+}

+ 2 - 2
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}?

+ 3 - 2
config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd

@@ -671,18 +671,19 @@
     </xs:attribute>
     <xs:attribute name="use-expressions" type="security:boolean">
       <xs:annotation>
-        <xs:documentation>Enables the use of expressions in the 'access' attributes in &lt;intercept-url&gt; 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. </xs:documentation>
+        <xs:documentation>Enables the use of expressions in the 'access' attributes in &lt;intercept-url&gt; 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.</xs:documentation>
       </xs:annotation>
     </xs:attribute>
     <xs:attribute name="create-session">
       <xs:annotation>
-        <xs:documentation>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.</xs:documentation>
+        <xs:documentation>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. </xs:documentation>
       </xs:annotation>
       <xs:simpleType>
         <xs:restriction base="xs:token">
           <xs:enumeration value="ifRequired"/>
           <xs:enumeration value="always"/>
           <xs:enumeration value="never"/>
+          <xs:enumeration value="stateless"/>
         </xs:restriction>
       </xs:simpleType>
     </xs:attribute>

+ 20 - 15
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("<http auto-config='true' create-session='stateless'/>" + 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("<http auto-config='true' create-session='ifRequired'/>" + 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(
-                "<b:bean id='repo' class='" + HttpSessionSecurityContextRepository.class.getName() + "'/>" +
-                "<http create-session='never' security-context-repository-ref='repo'>" +
-                "    <http-basic />" +
-                "</http>" + 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 {

+ 2 - 2
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) {

+ 26 - 0
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) {
+    }
+
+}