瀏覽代碼

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

Luke Taylor 15 年之前
父節點
當前提交
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) {
     public BeanDefinition parse(Element element, ParserContext pc) {
         if (!namespaceMatchesVersion(element)) {
         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);
         String name = pc.getDelegate().getLocalName(element);
         BeanDefinitionParser parser = parsers.get(name);
         BeanDefinitionParser parser = parsers.get(name);
@@ -131,7 +131,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
 
 
     private boolean matchesVersionInternal(Element element) {
     private boolean matchesVersionInternal(Element element) {
         String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
         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.xsd.*")
                  || !schemaLocation.matches("(?m).*spring-security.*");
                  || !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.authentication.RememberMeAuthenticationProvider;
 import org.springframework.security.config.Elements;
 import org.springframework.security.config.Elements;
 import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
 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.AccessDeniedHandlerImpl;
 import org.springframework.security.web.access.ExceptionTranslationFilter;
 import org.springframework.security.web.access.ExceptionTranslationFilter;
 import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
 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.SubjectDnX509PrincipalExtractor;
 import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
 import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
 import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
 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.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.StringUtils;
 import org.springframework.util.xml.DomUtils;
 import org.springframework.util.xml.DomUtils;
 import org.w3c.dom.Element;
 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_USER_SERVICE_REF = "user-service-ref";
 
 
-    private static final String ATT_REF = "ref";
-
     private Element httpElt;
     private Element httpElt;
     private ParserContext pc;
     private ParserContext pc;
 
 
     private final boolean autoConfig;
     private final boolean autoConfig;
     private final boolean allowSessionCreation;
     private final boolean allowSessionCreation;
-    private final String portMapperName;
 
 
     private RootBeanDefinition anonymousFilter;
     private RootBeanDefinition anonymousFilter;
     private BeanReference anonymousProviderRef;
     private BeanReference anonymousProviderRef;
@@ -101,19 +96,32 @@ final class AuthenticationConfigBuilder {
 
 
     final SecureRandom random;
     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.httpElt = element;
         this.pc = pc;
         this.pc = pc;
-        this.portMapperName = portMapperName;
+        this.requestCache = requestCache;
         autoConfig = "true".equals(element.getAttribute(ATT_AUTO_CONFIG));
         autoConfig = "true".equals(element.getAttribute(ATT_AUTO_CONFIG));
-        this.allowSessionCreation = allowSessionCreation;
+        this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.never
+                && sessionPolicy != SessionCreationPolicy.stateless;
         try {
         try {
             random = SecureRandom.getInstance("SHA1PRNG");
             random = SecureRandom.getInstance("SHA1PRNG");
         } catch (NoSuchAlgorithmException e) {
         } catch (NoSuchAlgorithmException e) {
             // Shouldn't happen...
             // Shouldn't happen...
             throw new RuntimeException("Failed find SHA1PRNG algorithm!");
             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) {
     void createRememberMeFilter(BeanReference authenticationManager) {
@@ -166,7 +174,6 @@ final class AuthenticationConfigBuilder {
             formFilter.getPropertyValues().addPropertyValue("allowSessionCreation", new Boolean(allowSessionCreation));
             formFilter.getPropertyValues().addPropertyValue("allowSessionCreation", new Boolean(allowSessionCreation));
             formFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager);
             formFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager);
 
 
-
             // Id is required by login page filter
             // Id is required by login page filter
             formFilterId = pc.getReaderContext().generateBeanName(formFilter);
             formFilterId = pc.getReaderContext().generateBeanName(formFilter);
             pc.registerBeanComponent(new BeanComponentDefinition(formFilter, formFilterId));
             pc.registerBeanComponent(new BeanComponentDefinition(formFilter, formFilterId));
@@ -323,7 +330,6 @@ final class AuthenticationConfigBuilder {
         x509ProviderRef = new RuntimeBeanReference(x509ProviderId);
         x509ProviderRef = new RuntimeBeanReference(x509ProviderId);
     }
     }
 
 
-
     void createLoginPageFilterIfNeeded() {
     void createLoginPageFilterIfNeeded() {
         boolean needLoginPage = formFilter != null || openIDFilter != null;
         boolean needLoginPage = formFilter != null || openIDFilter != null;
         String formLoginPage = getLoginFormUrl(formEntryPoint);
         String formLoginPage = getLoginFormUrl(formEntryPoint);
@@ -414,28 +420,6 @@ final class AuthenticationConfigBuilder {
         etf = etfBuilder.getBeanDefinition();
         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) {
     private BeanMetadataElement createAccessDeniedHandler(Element element, ParserContext pc) {
         String accessDeniedPage = element.getAttribute(ATT_ACCESS_DENIED_PAGE);
         String accessDeniedPage = element.getAttribute(ATT_ACCESS_DENIED_PAGE);
         WebConfigUtils.validateHttpRedirect(accessDeniedPage, pc, pc.extractSource(element));
         WebConfigUtils.validateHttpRedirect(accessDeniedPage, pc, pc.extractSource(element));
@@ -610,8 +594,4 @@ final class AuthenticationConfigBuilder {
         return providers;
         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;
 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.HttpSecurityBeanDefinitionParser.*;
+import static org.springframework.security.config.http.SecurityFilters.*;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 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.access.vote.RoleVoter;
 import org.springframework.security.config.Elements;
 import org.springframework.security.config.Elements;
 import org.springframework.security.core.session.SessionRegistryImpl;
 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.DefaultWebInvocationPrivilegeEvaluator;
 import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
 import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
 import org.springframework.security.web.access.channel.ChannelProcessingFilter;
 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.ConcurrentSessionControlStrategy;
 import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
 import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 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.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.servletapi.SecurityContextHolderAwareRequestFilter;
 import org.springframework.security.web.session.ConcurrentSessionFilter;
 import org.springframework.security.web.session.ConcurrentSessionFilter;
 import org.springframework.security.web.session.SessionManagementFilter;
 import org.springframework.security.web.session.SessionManagementFilter;
@@ -57,9 +62,6 @@ import org.w3c.dom.Element;
  */
  */
 class HttpConfigurationBuilder {
 class HttpConfigurationBuilder {
     private static final String ATT_CREATE_SESSION = "create-session";
     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 ATT_SESSION_FIXATION_PROTECTION = "session-fixation-protection";
     private static final String OPT_SESSION_FIXATION_NO_PROTECTION = "none";
     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_ACCESS_MGR = "access-decision-manager-ref";
     private static final String ATT_ONCE_PER_REQUEST = "once-per-request";
     private static final String ATT_ONCE_PER_REQUEST = "once-per-request";
 
 
+    private static final String ATT_REF = "ref";
+
     private final Element httpElt;
     private final Element httpElt;
     private final ParserContext pc;
     private final ParserContext pc;
     private final UrlMatcher matcher;
     private final UrlMatcher matcher;
     private final Boolean convertPathsToLowerCase;
     private final Boolean convertPathsToLowerCase;
-    private final boolean allowSessionCreation;
+    private final SessionCreationPolicy sessionPolicy;
     private final List<Element> interceptUrls;
     private final List<Element> interceptUrls;
 
 
     // Use ManagedMap to allow placeholder resolution
     // Use ManagedMap to allow placeholder resolution
@@ -90,13 +94,16 @@ class HttpConfigurationBuilder {
     private BeanReference contextRepoRef;
     private BeanReference contextRepoRef;
     private BeanReference sessionRegistryRef;
     private BeanReference sessionRegistryRef;
     private BeanDefinition concurrentSessionFilter;
     private BeanDefinition concurrentSessionFilter;
+    private BeanDefinition requestCacheAwareFilter;
     private BeanReference sessionStrategyRef;
     private BeanReference sessionStrategyRef;
     private RootBeanDefinition sfpf;
     private RootBeanDefinition sfpf;
     private BeanDefinition servApiFilter;
     private BeanDefinition servApiFilter;
     private String portMapperName;
     private String portMapperName;
     private BeanReference fsi;
     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.httpElt = element;
         this.pc = pc;
         this.pc = pc;
         this.portMapperName = portMapperName;
         this.portMapperName = portMapperName;
@@ -105,10 +112,24 @@ class HttpConfigurationBuilder {
         // true if Ant path and using lower case
         // true if Ant path and using lower case
         convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
         convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
         interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
         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>>();
         filterChainMap = new ManagedMap<BeanDefinition, List<BeanMetadataElement>>();
 
 
         for (Element urlElt : interceptUrls) {
         for (Element urlElt : interceptUrls) {
@@ -142,43 +163,44 @@ class HttpConfigurationBuilder {
         return lowerCase ? path.toLowerCase() : path;
         return lowerCase ? path.toLowerCase() : path;
     }
     }
 
 
-    void createSecurityContextPersistenceFilter() {
+    private void createSecurityContextPersistenceFilter() {
         BeanDefinitionBuilder scpf = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextPersistenceFilter.class);
         BeanDefinitionBuilder scpf = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextPersistenceFilter.class);
 
 
         String repoRef = httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY);
         String repoRef = httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY);
-        String createSession = httpElt.getAttribute(ATT_CREATE_SESSION);
         String disableUrlRewriting = httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
         String disableUrlRewriting = httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
 
 
         if (StringUtils.hasText(repoRef)) {
         if (StringUtils.hasText(repoRef)) {
-            if (OPT_CREATE_SESSION_ALWAYS.equals(createSession)) {
+            if (sessionPolicy == SessionCreationPolicy.always) {
                 scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE);
                 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 {
         } 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 {
             } 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();
             BeanDefinition repoBean = contextRepo.getBeanDefinition();
             repoRef = pc.getReaderContext().generateBeanName(repoBean);
             repoRef = pc.getReaderContext().generateBeanName(repoBean);
             pc.registerBeanComponent(new BeanComponentDefinition(repoBean, repoRef));
             pc.registerBeanComponent(new BeanComponentDefinition(repoBean, repoRef));
-
         }
         }
 
 
         contextRepoRef = new RuntimeBeanReference(repoRef);
         contextRepoRef = new RuntimeBeanReference(repoRef);
@@ -187,7 +209,7 @@ class HttpConfigurationBuilder {
         securityContextPersistenceFilter = scpf.getBeanDefinition();
         securityContextPersistenceFilter = scpf.getBeanDefinition();
     }
     }
 
 
-    void createSessionManagementFilters() {
+    private void createSessionManagementFilters() {
         Element sessionMgmtElt = DomUtils.getChildElementByTagName(httpElt, Elements.SESSION_MANAGEMENT);
         Element sessionMgmtElt = DomUtils.getChildElementByTagName(httpElt, Elements.SESSION_MANAGEMENT);
         Element sessionCtrlElt = null;
         Element sessionCtrlElt = null;
 
 
@@ -197,6 +219,11 @@ class HttpConfigurationBuilder {
         String errorUrl = null;
         String errorUrl = null;
 
 
         if (sessionMgmtElt != 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);
             sessionFixationAttribute = sessionMgmtElt.getAttribute(ATT_SESSION_FIXATION_PROTECTION);
             invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL);
             invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL);
             sessionAuthStratRef = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_STRATEGY_REF);
             sessionAuthStratRef = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_STRATEGY_REF);
@@ -216,7 +243,12 @@ class HttpConfigurationBuilder {
             sessionFixationAttribute = OPT_SESSION_FIXATION_MIGRATE_SESSION;
             sessionFixationAttribute = OPT_SESSION_FIXATION_MIGRATE_SESSION;
         } else if (StringUtils.hasText(sessionAuthStratRef)) {
         } else if (StringUtils.hasText(sessionAuthStratRef)) {
             pc.getReaderContext().error(ATT_SESSION_FIXATION_PROTECTION + " attribute cannot be used" +
             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);
         boolean sessionFixationProtectionRequired = !sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION);
@@ -323,7 +355,7 @@ class HttpConfigurationBuilder {
     }
     }
 
 
     // Adds the servlet-api integration filter if required
     // Adds the servlet-api integration filter if required
-    void createServletApiFilter() {
+    private void createServletApiFilter() {
         final String ATT_SERVLET_API_PROVISION = "servlet-api-provision";
         final String ATT_SERVLET_API_PROVISION = "servlet-api-provision";
         final String DEF_SERVLET_API_PROVISION = "true";
         final String DEF_SERVLET_API_PROVISION = "true";
 
 
@@ -337,7 +369,7 @@ class HttpConfigurationBuilder {
         }
         }
     }
     }
 
 
-    void createChannelProcessingFilter() {
+    private void createChannelProcessingFilter() {
         ManagedMap<BeanDefinition,BeanDefinition> channelRequestMap = parseInterceptUrlsForChannelSecurity();
         ManagedMap<BeanDefinition,BeanDefinition> channelRequestMap = parseInterceptUrlsForChannelSecurity();
 
 
         if (channelRequestMap.isEmpty()) {
         if (channelRequestMap.isEmpty()) {
@@ -407,7 +439,36 @@ class HttpConfigurationBuilder {
         return channelRequestMap;
         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);
         boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);
         BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);
         BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);
 
 
@@ -459,12 +520,15 @@ class HttpConfigurationBuilder {
         return sessionStrategyRef;
         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;
         return filterChainMap;
     }
     }
 
 
@@ -491,6 +555,10 @@ class HttpConfigurationBuilder {
 
 
         filters.add(new OrderDecorator(fsi, FILTER_SECURITY_INTERCEPTOR));
         filters.add(new OrderDecorator(fsi, FILTER_SECURITY_INTERCEPTOR));
 
 
+        if (sessionPolicy != SessionCreationPolicy.stateless) {
+            filters.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER));
+        }
+
         return filters;
         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;
 package org.springframework.security.config.http;
 
 
-import static org.springframework.security.config.http.SecurityFilters.REQUEST_CACHE_FILTER;
-
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 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.Elements;
 import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean;
 import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean;
 import org.springframework.security.web.FilterChainProxy;
 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.AntUrlPathMatcher;
 import org.springframework.security.web.util.RegexUrlPathMatcher;
 import org.springframework.security.web.util.RegexUrlPathMatcher;
 import org.springframework.security.web.util.UrlMatcher;
 import org.springframework.security.web.util.UrlMatcher;
@@ -85,33 +82,15 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
         final String portMapperName = createPortMapper(element, pc);
         final String portMapperName = createPortMapper(element, pc);
         final UrlMatcher matcher = createUrlMatcher(element);
         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>();
         ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
         BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null);
         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,
         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>();
         List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
 
 
@@ -120,10 +99,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
 
 
         authenticationProviders.addAll(authBldr.getProviders());
         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));
         unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
 
 
         Collections.sort(unorderedFilterChain, new OrderComparator());
         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 &=
 http.attlist &=
     use-expressions?
     use-expressions?
 http.attlist &=
 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 &=
 http.attlist &=
     ## A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests.
     ## 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}?
     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>
     <xs:attribute name="use-expressions" type="security:boolean">
     <xs:attribute name="use-expressions" type="security:boolean">
       <xs:annotation>
       <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:annotation>
     </xs:attribute>
     </xs:attribute>
     <xs:attribute name="create-session">
     <xs:attribute name="create-session">
       <xs:annotation>
       <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:annotation>
       <xs:simpleType>
       <xs:simpleType>
         <xs:restriction base="xs:token">
         <xs:restriction base="xs:token">
           <xs:enumeration value="ifRequired"/>
           <xs:enumeration value="ifRequired"/>
           <xs:enumeration value="always"/>
           <xs:enumeration value="always"/>
           <xs:enumeration value="never"/>
           <xs:enumeration value="never"/>
+          <xs:enumeration value="stateless"/>
         </xs:restriction>
         </xs:restriction>
       </xs:simpleType>
       </xs:simpleType>
     </xs:attribute>
     </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.ui.DefaultLoginPageGeneratingFilter;
 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 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.SaveContextOnUpdateOrErrorResponseWrapper;
 import org.springframework.security.web.context.SecurityContextPersistenceFilter;
 import org.springframework.security.web.context.SecurityContextPersistenceFilter;
 import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
 import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
@@ -958,6 +959,23 @@ public class HttpSecurityBeanDefinitionParserTests {
         assertNull(request.getSession(false));
         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
     @Test
     public void settingCreateSessionToIfRequiredDoesntCreateASessionForPublicInvocation() throws Exception {
     public void settingCreateSessionToIfRequiredDoesntCreateASessionForPublicInvocation() throws Exception {
         setContext("<http auto-config='true' create-session='ifRequired'/>" + AUTH_PROVIDER_XML);
         setContext("<http auto-config='true' create-session='ifRequired'/>" + AUTH_PROVIDER_XML);
@@ -1002,15 +1020,6 @@ public class HttpSecurityBeanDefinitionParserTests {
         assertTrue((Boolean)FieldUtils.getFieldValue(filter, "forceEagerSessionCreation"));
         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
     @Test
     public void expressionBasedAccessAllowsAndDeniesAccessAsExpected() throws Exception {
     public void expressionBasedAccessAllowsAndDeniesAccessAsExpected() throws Exception {
         setContext(
         setContext(
@@ -1147,11 +1156,7 @@ public class HttpSecurityBeanDefinitionParserTests {
         ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class);
         ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class);
         LoginUrlAuthenticationEntryPoint ap = (LoginUrlAuthenticationEntryPoint) etf.getAuthenticationEntryPoint();
         LoginUrlAuthenticationEntryPoint ap = (LoginUrlAuthenticationEntryPoint) etf.getAuthenticationEntryPoint();
         assertEquals("/form_login_page", ap.getLoginFormUrl());
         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
     @Test
@@ -1250,7 +1255,7 @@ public class HttpSecurityBeanDefinitionParserTests {
             }
             }
         }
         }
 
 
-        throw new Exception("Filter not found");
+        return null;
     }
     }
 
 
     private RememberMeServices getRememberMeServices() throws Exception {
     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;
     Resource inMemoryXml;
 
 
     public InMemoryXmlApplicationContext(String xml) {
     public InMemoryXmlApplicationContext(String xml) {
-        this(xml, "3.0", null);
+        this(xml, "3.1", null);
     }
     }
 
 
     public InMemoryXmlApplicationContext(String xml, ApplicationContext parent) {
     public InMemoryXmlApplicationContext(String xml, ApplicationContext parent) {
-        this(xml, "3.0", parent);
+        this(xml, "3.1", parent);
     }
     }
 
 
     public InMemoryXmlApplicationContext(String xml, String secVersion, ApplicationContext 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) {
+    }
+
+}