Преглед на файлове

SEC-1998: Provide integration with WebAsyncManager#startCallableProcessing

Support integration of the Spring SecurityContext on Callable's used with
WebAsyncManager by registering SecurityContextCallableProcessingInterceptor.
Rob Winch преди 12 години
родител
ревизия
1ed643ca1f
променени са 29 файла, в които са добавени 583 реда и са изтрити 35 реда
  1. 2 2
      cas/cas.gradle
  2. 1 1
      config/config.gradle
  3. 17 1
      config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
  4. 17 0
      config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
  5. 18 1
      config/src/test/groovy/org/springframework/security/config/http/AbstractHttpConfigTests.groovy
  6. 6 3
      config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
  7. 1 1
      config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy
  8. 49 0
      core/src/test/java/org/springframework/security/core/JavaVersionTests.java
  9. 23 2
      gradle/ide-integration.gradle
  10. 1 0
      gradle/javaprojects.gradle
  11. 1 1
      itest/context/itest-context.gradle
  12. 1 1
      itest/web/itest-web.gradle
  13. 1 1
      openid/openid.gradle
  14. 1 1
      samples/cas/sample/cassample.gradle
  15. 1 1
      samples/contacts/contacts.gradle
  16. 1 1
      samples/gae/gae.gradle
  17. 1 1
      samples/jaas/jaas.gradle
  18. 1 1
      samples/openid/openid.gradle
  19. 1 1
      samples/tutorial/tutorial.gradle
  20. 2 2
      sandbox/heavyduty/build.gradle
  21. 3 2
      taglibs/taglibs.gradle
  22. 79 3
      web/src/main/java/org/springframework/security/web/FilterInvocation.java
  23. 79 0
      web/src/main/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptor.java
  24. 52 0
      web/src/main/java/org/springframework/security/web/context/request/async/WebAsyncManagerIntegrationFilter.java
  25. 1 1
      web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java
  26. 18 6
      web/src/test/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServicesTests.java
  27. 77 0
      web/src/test/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptorTests.java
  28. 127 0
      web/src/test/java/org/springframework/security/web/context/request/async/WebAsyncManagerIntegrationFilterTests.java
  29. 1 1
      web/web.gradle

+ 2 - 2
cas/cas.gradle

@@ -8,5 +8,5 @@ dependencies {
             "org.jasig.cas.client:cas-client-core:3.1.12",
             "net.sf.ehcache:ehcache:$ehcacheVersion"
 
-    provided 'javax.servlet:servlet-api:2.5'
-}            
+    provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
+}

+ 1 - 1
config/config.gradle

@@ -21,7 +21,7 @@ dependencies {
             "org.springframework:spring-web:$springVersion",
             "org.springframework:spring-beans:$springVersion"
 
-    provided "javax.servlet:servlet-api:2.5"
+    provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     groovy 'org.codehaus.groovy:groovy:1.8.7'
 

+ 17 - 1
config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

@@ -21,6 +21,8 @@ import static org.springframework.security.config.http.SecurityFilters.*;
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.servlet.ServletRequest;
+
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.BeanReference;
 import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -53,6 +55,7 @@ import org.springframework.security.web.authentication.session.SessionFixationPr
 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.request.async.WebAsyncManagerIntegrationFilter;
 import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
 import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
 import org.springframework.security.web.savedrequest.NullRequestCache;
@@ -61,6 +64,7 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareReq
 import org.springframework.security.web.session.ConcurrentSessionFilter;
 import org.springframework.security.web.session.SessionManagementFilter;
 import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
+import org.springframework.util.ClassUtils;
 import org.springframework.util.StringUtils;
 import org.springframework.util.xml.DomUtils;
 import org.w3c.dom.Element;
@@ -102,6 +106,7 @@ class HttpConfigurationBuilder {
     private BeanReference contextRepoRef;
     private BeanReference sessionRegistryRef;
     private BeanDefinition concurrentSessionFilter;
+    private BeanDefinition webAsyncManagerFilter;
     private BeanDefinition requestCacheAwareFilter;
     private BeanReference sessionStrategyRef;
     private RootBeanDefinition sfpf;
@@ -114,7 +119,6 @@ class HttpConfigurationBuilder {
 
     public HttpConfigurationBuilder(Element element, ParserContext pc,
             BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
-
         this.httpElt = element;
         this.pc = pc;
         this.portMapper = portMapper;
@@ -140,6 +144,7 @@ class HttpConfigurationBuilder {
 
         createSecurityContextPersistenceFilter();
         createSessionManagementFilters();
+        createWebAsyncManagerFilter();
         createRequestCacheFilter();
         createServletApiFilter();
         createJaasApiFilter();
@@ -350,6 +355,13 @@ class HttpConfigurationBuilder {
         sessionRegistryRef = new RuntimeBeanReference(sessionRegistryId);
     }
 
+    private void createWebAsyncManagerFilter() {
+        boolean asyncSupported = ClassUtils.hasMethod(ServletRequest.class, "startAsync");
+        if(asyncSupported) {
+            webAsyncManagerFilter = new RootBeanDefinition(WebAsyncManagerIntegrationFilter.class);
+        }
+    }
+
     // Adds the servlet-api integration filter if required
     private void createServletApiFilter() {
         final String ATT_SERVLET_API_PROVISION = "servlet-api-provision";
@@ -552,6 +564,10 @@ class HttpConfigurationBuilder {
             filters.add(new OrderDecorator(concurrentSessionFilter, CONCURRENT_SESSION_FILTER));
         }
 
+        if (webAsyncManagerFilter != null) {
+            filters.add(new OrderDecorator(webAsyncManagerFilter, WEB_ASYNC_MANAGER_FILTER));
+        }
+
         filters.add(new OrderDecorator(securityContextPersistenceFilter, SECURITY_CONTEXT_FILTER));
 
         if (servApiFilter != null) {

+ 17 - 0
config/src/main/java/org/springframework/security/config/http/SecurityFilters.java

@@ -1,10 +1,25 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
 package org.springframework.security.config.http;
 
+import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
+
 
 /**
  * Stores the default order numbers of all Spring Security filters for use in configuration.
  *
  * @author Luke Taylor
+ * @author Rob Winch
  */
 
 enum SecurityFilters {
@@ -12,6 +27,8 @@ enum SecurityFilters {
     CHANNEL_FILTER,
     SECURITY_CONTEXT_FILTER,
     CONCURRENT_SESSION_FILTER,
+    /** {@link WebAsyncManagerIntegrationFilter} */
+    WEB_ASYNC_MANAGER_FILTER,
     LOGOUT_FILTER,
     X509_FILTER,
     PRE_AUTH_FILTER,

+ 18 - 1
config/src/test/groovy/org/springframework/security/config/http/AbstractHttpConfigTests.groovy

@@ -1,3 +1,15 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
 package org.springframework.security.config.http
 
 import javax.servlet.Filter
@@ -8,8 +20,13 @@ import org.springframework.security.config.AbstractXmlConfigTests
 import org.springframework.security.config.BeanIds
 import org.springframework.security.web.FilterInvocation
 
+/**
+ *
+ * @author Rob Winch
+ *
+ */
 abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
-    final int AUTO_CONFIG_FILTERS = 11;
+    final int AUTO_CONFIG_FILTERS = 12;
 
     def httpAutoConfig(Closure c) {
         xml.http('auto-config': 'true', c)

+ 6 - 3
config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy

@@ -54,6 +54,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationEn
 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository
 import org.springframework.security.web.context.SecurityContextPersistenceFilter
+import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
 import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter
 import org.springframework.security.web.savedrequest.HttpSessionRequestCache
 import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
@@ -79,6 +80,7 @@ import org.springframework.security.authentication.AuthenticationManager
  * @author Rob Winch
  */
 class MiscHttpConfigTests extends AbstractHttpConfigTests {
+
     def 'Minimal configuration parses'() {
         setup:
         xml.http {
@@ -101,6 +103,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
         Iterator<Filter> filters = filterList.iterator();
 
         assert filters.next() instanceof SecurityContextPersistenceFilter
+        assert filters.next() instanceof WebAsyncManagerIntegrationFilter
         assert filters.next() instanceof LogoutFilter
         Object authProcFilter = filters.next();
         assert authProcFilter instanceof UsernamePasswordAuthenticationFilter
@@ -181,7 +184,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
         createAppContext()
 
         expect:
-        getFilters("/anything")[5] instanceof AnonymousAuthenticationFilter
+        getFilters("/anything")[6] instanceof AnonymousAuthenticationFilter
     }
 
     def anonymousFilterIsRemovedIfDisabledFlagSet() {
@@ -354,7 +357,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
         AUTO_CONFIG_FILTERS + 3 == filters.size();
         filters[0] instanceof SecurityContextHolderAwareRequestFilter
         filters[1] instanceof SecurityContextPersistenceFilter
-        filters[4] instanceof SecurityContextHolderAwareRequestFilter
+        filters[5] instanceof SecurityContextHolderAwareRequestFilter
         filters[1] instanceof SecurityContextPersistenceFilter
     }
 
@@ -377,7 +380,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
         createAppContext()
 
         expect:
-        getFilters("/someurl")[2] instanceof X509AuthenticationFilter
+        getFilters("/someurl")[3] instanceof X509AuthenticationFilter
     }
 
     def x509SubjectPrincipalRegexCanBeSetUsingPropertyPlaceholder() {

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy

@@ -305,7 +305,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
             'session-management'('session-fixation-protection': 'none', 'invalid-session-url': '/timeoutUrl')
         }
         createAppContext()
-        def filter = getFilters("/someurl")[8]
+        def filter = getFilters("/someurl")[9]
 
         expect:
         filter instanceof SessionManagementFilter

+ 49 - 0
core/src/test/java/org/springframework/security/core/JavaVersionTests.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.springframework.security.core;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import java.io.DataInputStream;
+import java.io.InputStream;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+public class JavaVersionTests {
+
+    private static final int JDK5_CLASS_VERSION = 49;
+
+    @Test
+    public void authenticationCorrectJdkCompatibility() throws Exception {
+        assertClassVersion(Authentication.class);
+    }
+
+    private void assertClassVersion(Class<?> clazz) throws Exception {
+        String classResourceName = clazz.getName().replaceAll("\\.", "/") + ".class";
+        InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(classResourceName);
+        try {
+            DataInputStream data = new DataInputStream(input);
+            data.readInt();
+            data.readShort(); // minor
+            int major = data.readShort();
+            assertThat(major).isEqualTo(JDK5_CLASS_VERSION);
+        } finally {
+            try { input.close(); } catch(Exception e) {}
+        }
+    }
+}

+ 23 - 2
gradle/ide-integration.gradle

@@ -39,8 +39,8 @@ configure(javaProjects) {
     }
 }
 
-// STS-2723
-project(':spring-security-samples-aspectj') {
+// STS-3057
+configure(allprojects) {
     task afterEclipseImport {
         ext.srcFile = file('.classpath')
         inputs.file srcFile
@@ -48,6 +48,25 @@ project(':spring-security-samples-aspectj') {
 
         onlyIf { srcFile.exists() }
 
+        doLast {
+            def classpath = new XmlParser().parse(srcFile)
+            classpath.classpathentry.findAll{ it.@path == 'GROOVY_SUPPORT' }.each { classpath.remove(it) }
+
+            def writer = new FileWriter(srcFile)
+            new XmlNodePrinter(new PrintWriter(writer)).print(classpath)
+        }
+    }
+}
+
+// STS-2723
+project(':spring-security-samples-aspectj') {
+    task afterEclipseImportAjdtFix {
+        ext.srcFile = afterEclipseImport.srcFile
+        inputs.file srcFile
+        outputs.dir srcFile
+
+        onlyIf { srcFile.exists() }
+
         doLast {
             def classpath = new XmlParser().parse(srcFile)
 
@@ -63,4 +82,6 @@ project(':spring-security-samples-aspectj') {
             new XmlNodePrinter(new PrintWriter(writer)).print(classpath)
         }
     }
+    afterEclipseImport.dependsOn afterEclipseImportAjdtFix
 }
+

+ 1 - 0
gradle/javaprojects.gradle

@@ -16,6 +16,7 @@ ext.slf4jVersion = '1.6.1'
 ext.logbackVersion = '0.9.29'
 ext.cglibVersion = '2.2'
 ext.powerMockVersion = '1.4.12'
+ext.servletApiVersion = '7.0.33'
 
 ext.powerMockDependencies = [
     "org.powermock:powermock-core:$powerMockVersion",

+ 1 - 1
itest/context/itest-context.gradle

@@ -10,7 +10,7 @@ dependencies {
                 "org.springframework:spring-beans:$springVersion"
 
     testCompile project(':spring-security-web'),
-                'javax.servlet:servlet-api:2.5',
+                "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion",
                 "org.springframework:spring-web:$springVersion"
     testRuntime project(':spring-security-config')
 

+ 1 - 1
itest/web/itest-web.gradle

@@ -3,7 +3,7 @@ dependencies {
     compile     "org.springframework:spring-context:$springVersion",
                 "org.springframework:spring-web:$springVersion"
 
-    provided    'javax.servlet:servlet-api:2.5'
+    provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     testCompile project(':spring-security-core'),
                 project(':spring-security-web'),

+ 1 - 1
openid/openid.gradle

@@ -16,7 +16,7 @@ dependencies {
     }
     compile 'com.google.inject:guice:2.0'
 
-    provided 'javax.servlet:servlet-api:2.5'
+    provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     runtime 'org.apache.httpcomponents:httpclient:4.1.1'
 }

+ 1 - 1
samples/cas/sample/cassample.gradle

@@ -30,7 +30,7 @@ eclipse.classpath.plusConfigurations += configurations.integrationTestRuntime
 dependencies {
     groovy 'org.codehaus.groovy:groovy:1.8.7'
 
-    providedCompile 'javax.servlet:servlet-api:2.5@jar'
+    providedCompile "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     compile project(':spring-security-core'),
             project(':spring-security-cas'),

+ 1 - 1
samples/contacts/contacts.gradle

@@ -9,7 +9,7 @@ configurations {
 }
 
 dependencies {
-    providedCompile 'javax.servlet:servlet-api:2.5@jar'
+    providedCompile "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     compile project(':spring-security-core'),
             project(':spring-security-acl'),

+ 1 - 1
samples/gae/gae.gradle

@@ -21,7 +21,7 @@ repositories {
 configurations.runtime.exclude(group: 'ch.qos.logback')
 
 dependencies {
-    providedCompile 'javax.servlet:servlet-api:2.5@jar'
+    providedCompile "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     compile project(':spring-security-core'),
             project(':spring-security-web'),

+ 1 - 1
samples/jaas/jaas.gradle

@@ -14,7 +14,7 @@ configurations {
 }
 
 dependencies {
-    providedCompile 'javax.servlet:servlet-api:2.5@jar'
+    providedCompile "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     compile project(':spring-security-core'),
             "org.springframework:spring-beans:$springVersion",

+ 1 - 1
samples/openid/openid.gradle

@@ -7,7 +7,7 @@ dependencies {
     compile project(':spring-security-core'),
             project(':spring-security-openid')
 
-    providedCompile 'javax.servlet:servlet-api:2.5@jar'
+    providedCompile "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     runtime project(':spring-security-config'),
             project(':spring-security-taglibs'),

+ 1 - 1
samples/tutorial/tutorial.gradle

@@ -14,7 +14,7 @@ configurations {
 }
 
 dependencies {
-    providedCompile 'javax.servlet:servlet-api:2.5@jar'
+    providedCompile "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     compile project(':spring-security-core'),
             "org.springframework:spring-beans:$springVersion",

+ 2 - 2
sandbox/heavyduty/build.gradle

@@ -24,9 +24,9 @@ dependencies {
             'org.aspectj:aspectjrt:1.6.8',
             'org.hibernate:ejb3-persistence:1.0.2.GA',
             'javax.persistence:persistence-api:1.0',
-            'org.slf4j:jcl-over-slf4j:1.5.11'            
+            'org.slf4j:jcl-over-slf4j:1.5.11'
 
-    providedCompile 'javax.servlet:servlet-api:2.5'
+    providedCompile "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     runtime 'org.hibernate:hibernate-entitymanager:3.4.0.GA',
             "org.springframework:spring-context-support:$springVersion",

+ 3 - 2
taglibs/taglibs.gradle

@@ -10,7 +10,8 @@ dependencies {
             "org.springframework:spring-expression:$springVersion",
             "org.springframework:spring-web:$springVersion"
 
-    provided 'javax.servlet:jsp-api:2.0', 'javax.servlet:servlet-api:2.5'
-    
+    provided 'javax.servlet:jsp-api:2.0',
+             "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
+
     testRuntime "javax.servlet:jstl:$jstlVersion"
 }

+ 79 - 3
web/src/main/java/org/springframework/security/web/FilterInvocation.java

@@ -1,4 +1,5 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+/* Copyright 2002-2012 the original author or authors.
+ * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,12 +21,16 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
 import java.security.Principal;
+import java.util.Collection;
 import java.util.Enumeration;
 import java.util.Locale;
 import java.util.Map;
 
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
 import javax.servlet.FilterChain;
 import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletInputStream;
 import javax.servlet.ServletOutputStream;
@@ -35,6 +40,7 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
 
 import org.springframework.security.web.util.UrlUtils;
 
@@ -50,6 +56,7 @@ import org.springframework.security.web.util.UrlUtils;
  * @author Ben Alex
  * @author colin sampaleanu
  * @author Luke Taylor
+ * @author Rob Winch
  */
 public class FilterInvocation {
     //~ Static fields ==================================================================================================
@@ -147,7 +154,7 @@ public class FilterInvocation {
     }
 }
 
-@SuppressWarnings({"unchecked", "deprecation"})
+@SuppressWarnings({"unchecked"})
 class DummyRequest implements HttpServletRequest {
     private String requestURI;
     private String contextPath = "";
@@ -221,10 +228,12 @@ class DummyRequest implements HttpServletRequest {
         throw new UnsupportedOperationException();
     }
 
+    @SuppressWarnings("rawtypes")
     public Enumeration getHeaderNames() {
         throw new UnsupportedOperationException();
     }
 
+    @SuppressWarnings("rawtypes")
     public Enumeration getHeaders(String name) {
         throw new UnsupportedOperationException();
     }
@@ -285,6 +294,7 @@ class DummyRequest implements HttpServletRequest {
         throw new UnsupportedOperationException();
     }
 
+    @SuppressWarnings("rawtypes")
     public Enumeration getAttributeNames() {
         throw new UnsupportedOperationException();
     }
@@ -322,6 +332,7 @@ class DummyRequest implements HttpServletRequest {
         throw new UnsupportedOperationException();
     }
 
+    @SuppressWarnings("rawtypes")
     public Enumeration getLocales() {
         throw new UnsupportedOperationException();
     }
@@ -330,10 +341,12 @@ class DummyRequest implements HttpServletRequest {
         throw new UnsupportedOperationException();
     }
 
+    @SuppressWarnings("rawtypes")
     public Map getParameterMap() {
         throw new UnsupportedOperationException();
     }
 
+    @SuppressWarnings("rawtypes")
     public Enumeration getParameterNames() {
         throw new UnsupportedOperationException();
     }
@@ -397,9 +410,56 @@ class DummyRequest implements HttpServletRequest {
     public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
         throw new UnsupportedOperationException();
     }
+
+    public ServletContext getServletContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    public AsyncContext startAsync() {
+        throw new UnsupportedOperationException();
+    }
+
+    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isAsyncStarted() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isAsyncSupported() {
+        throw new UnsupportedOperationException();
+    }
+
+    public AsyncContext getAsyncContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    public DispatcherType getDispatcherType() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void login(String username, String password) throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void logout() throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Part getPart(String name) throws IOException, IllegalStateException, ServletException {
+        throw new UnsupportedOperationException();
+    }
 }
 
-@SuppressWarnings({"deprecation"})
 class DummyResponse implements HttpServletResponse {
     public void addCookie(Cookie cookie) {
         throw new UnsupportedOperationException();
@@ -529,4 +589,20 @@ class DummyResponse implements HttpServletResponse {
     public void setLocale(Locale loc) {
         throw new UnsupportedOperationException();
     }
+
+    public int getStatus() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getHeader(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Collection<String> getHeaders(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Collection<String> getHeaderNames() {
+        throw new UnsupportedOperationException();
+    }
 }

+ 79 - 0
web/src/main/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptor.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.springframework.security.web.context.request.async;
+
+import java.util.concurrent.Callable;
+
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.Assert;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.async.CallableProcessingInterceptor;
+import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
+
+/**
+ * <p>
+ * Allows for integration with Spring MVC's {@link Callable} support.
+ * </p>
+ * <p>
+ * A {@link CallableProcessingInterceptor} that establishes the injected {@link SecurityContext} on the
+ * {@link SecurityContextHolder} when {@link #preProcess(NativeWebRequest, Callable)} is invoked. It also clear out the
+ * {@link SecurityContextHolder} by invoking {@link SecurityContextHolder#clearContext()} in the
+ * {@link #afterCompletion(NativeWebRequest, Callable)} method.
+ * </p>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class SecurityContextCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter {
+    private SecurityContext securityContext;
+
+    /**
+     * Create a new {@link SecurityContextCallableProcessingInterceptor} that uses the {@link SecurityContext} from the
+     * {@link SecurityContextHolder} at the time {@link #beforeConcurrentHandling(NativeWebRequest, Callable)} is invoked.
+     */
+    public SecurityContextCallableProcessingInterceptor() {
+    }
+
+    /**
+     * Creates a new {@link SecurityContextCallableProcessingInterceptor} with the specified {@link SecurityContext}.
+     * @param securityContext the {@link SecurityContext} to set on the {@link SecurityContextHolder} in
+     * {@link #preProcess(NativeWebRequest, Callable)}. Cannot be null.
+     * @throws IllegalArgumentException if {@link SecurityContext} is null.
+     */
+    public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) {
+        Assert.notNull(securityContext, "securityContext cannot be null");
+        setSecurityContext(securityContext);
+    }
+
+    @Override
+    public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception {
+        if(securityContext == null) {
+            setSecurityContext(SecurityContextHolder.getContext());
+        }
+    }
+
+    @Override
+    public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
+        SecurityContextHolder.clearContext();
+    }
+
+    @Override
+    public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
+        SecurityContextHolder.setContext(securityContext);
+    }
+
+    private void setSecurityContext(SecurityContext securityContext) {
+        this.securityContext = securityContext;
+    }
+}

+ 52 - 0
web/src/main/java/org/springframework/security/web/context/request/async/WebAsyncManagerIntegrationFilter.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.springframework.security.web.context.request.async;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.web.context.request.async.WebAsyncManager;
+import org.springframework.web.context.request.async.WebAsyncUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/**
+ * Provides integration between the {@link SecurityContext} and Spring Web's {@link WebAsyncManager} by using the
+ * {@link SecurityContextCallableProcessingInterceptor#beforeConcurrentHandling(org.springframework.web.context.request.NativeWebRequest, Callable)}
+ * to populate the {@link SecurityContext} on the {@link Callable}.
+ *
+ * @author Rob Winch
+ * @see SecurityContextCallableProcessingInterceptor
+ */
+public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
+    private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+            throws ServletException, IOException {
+        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
+
+        SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
+        if (securityProcessingInterceptor == null) {
+            asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
+                    new SecurityContextCallableProcessingInterceptor());
+        }
+
+        filterChain.doFilter(request, response);
+    }
+}

+ 1 - 1
web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java

@@ -110,7 +110,7 @@ public class DefaultSavedRequest implements SavedRequest {
         }
 
         // Parameters
-        Map<String,Object> parameters = request.getParameterMap();
+        Map<String,String[]> parameters = request.getParameterMap();
 
         for(String paramName : parameters.keySet()) {
             Object paramValues = parameters.get(paramName);

+ 18 - 6
web/src/test/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServicesTests.java

@@ -1,13 +1,23 @@
 package org.springframework.security.web.authentication.rememberme;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.powermock.api.mockito.PowerMockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
@@ -19,17 +29,16 @@ import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
-import org.springframework.security.web.authentication.rememberme.CookieTheftException;
-import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
-import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;
 import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.util.ReflectionUtils;
 import org.springframework.util.StringUtils;
 
 /**
  * @author Luke Taylor
  */
 @SuppressWarnings("unchecked")
+@RunWith(PowerMockRunner.class)
+@PrepareOnlyThisForTest(ReflectionUtils.class)
 public class AbstractRememberMeServicesTests {
     static User joe = new User("joe", "password", true, true,true,true, AuthorityUtils.createAuthorityList("ROLE_A"));
 
@@ -333,6 +342,9 @@ public class AbstractRememberMeServicesTests {
 
     @Test
     public void setHttpOnlyIgnoredForServlet25() throws Exception {
+        spy(ReflectionUtils.class);
+        when(ReflectionUtils.findMethod(Cookie.class,"setHttpOnly", boolean.class)).thenReturn(null);
+
         MockRememberMeServices services = new MockRememberMeServices();
         assertNull(ReflectionTestUtils.getField(services, "setHttpOnlyMethod"));
 

+ 77 - 0
web/src/test/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptorTests.java

@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.springframework.security.web.context.request.async;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import java.util.concurrent.Callable;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.context.request.NativeWebRequest;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class SecurityContextCallableProcessingInterceptorTests {
+    @Mock
+    private SecurityContext securityContext;
+    @Mock
+    private Callable<?> callable;
+    @Mock
+    private NativeWebRequest webRequest;
+
+    @After
+    public void clearSecurityContext() {
+        SecurityContextHolder.clearContext();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void constructorNull() {
+        new SecurityContextCallableProcessingInterceptor(null);
+    }
+
+    @Test
+    public void currentSecurityContext() throws Exception {
+        SecurityContextCallableProcessingInterceptor interceptor = new SecurityContextCallableProcessingInterceptor();
+        SecurityContextHolder.setContext(securityContext);
+        interceptor.beforeConcurrentHandling(webRequest, callable);
+        SecurityContextHolder.clearContext();
+
+        interceptor.preProcess(webRequest, callable);
+        assertThat(SecurityContextHolder.getContext()).isSameAs(securityContext);
+
+        interceptor.afterCompletion(webRequest, callable);
+        assertThat(SecurityContextHolder.getContext()).isNotSameAs(securityContext);
+    }
+
+    @Test
+    public void specificSecurityContext() throws Exception {
+        SecurityContextCallableProcessingInterceptor interceptor = new SecurityContextCallableProcessingInterceptor(
+                securityContext);
+
+        interceptor.preProcess(webRequest, callable);
+        assertThat(SecurityContextHolder.getContext()).isSameAs(securityContext);
+
+        interceptor.afterCompletion(webRequest, callable);
+        assertThat(SecurityContextHolder.getContext()).isNotSameAs(securityContext);
+    }
+}

+ 127 - 0
web/src/test/java/org/springframework/security/web/context/request/async/WebAsyncManagerIntegrationFilterTests.java

@@ -0,0 +1,127 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.springframework.security.web.context.request.async;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ThreadFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.context.request.async.AsyncWebRequest;
+import org.springframework.web.context.request.async.WebAsyncManager;
+import org.springframework.web.context.request.async.WebAsyncUtils;
+
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class WebAsyncManagerIntegrationFilterTests {
+    @Mock
+    private SecurityContext securityContext;
+    @Mock
+    private HttpServletRequest request;
+    @Mock
+    private HttpServletResponse response;
+    @Mock
+    private AsyncWebRequest asyncWebRequest;
+    private WebAsyncManager asyncManager;
+    private JoinableThreadFactory threadFactory;
+
+    private MockFilterChain filterChain;
+
+    private WebAsyncManagerIntegrationFilter filter;
+
+    @Before
+    public void setUp() {
+        when(asyncWebRequest.getNativeRequest(HttpServletRequest.class)).thenReturn(request);
+        when(request.getRequestURI()).thenReturn("/");
+        filterChain = new MockFilterChain();
+
+        threadFactory = new JoinableThreadFactory();
+        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
+        executor.setThreadFactory(threadFactory);
+
+        asyncManager = WebAsyncUtils.getAsyncManager(request);
+        asyncManager.setAsyncWebRequest(asyncWebRequest);
+        asyncManager.setTaskExecutor(executor);
+        when(request.getAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE)).thenReturn(asyncManager);
+
+        filter = new WebAsyncManagerIntegrationFilter();
+    }
+
+    @After
+    public void clearSecurityContext() {
+        SecurityContextHolder.clearContext();
+    }
+
+    @Test
+    public void doFilterInternalRegistersSecurityContextCallableProcessor() throws Exception {
+        SecurityContextHolder.setContext(securityContext);
+        filter.doFilterInternal(request, response, filterChain);
+
+        VerifyingCallable verifyingCallable = new VerifyingCallable();
+        asyncManager.startCallableProcessing(verifyingCallable);
+        threadFactory.join();
+        assertThat(asyncManager.getConcurrentResult()).isSameAs(securityContext);
+    }
+
+    @Test
+    public void doFilterInternalRegistersSecurityContextCallableProcessorContextUpdated() throws Exception {
+        SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
+        filter.doFilterInternal(request, response, filterChain);
+        SecurityContextHolder.setContext(securityContext);
+
+        VerifyingCallable verifyingCallable = new VerifyingCallable();
+        asyncManager.startCallableProcessing(verifyingCallable);
+        threadFactory.join();
+        assertThat(asyncManager.getConcurrentResult()).isSameAs(securityContext);
+    }
+
+    private static final class JoinableThreadFactory implements ThreadFactory {
+        private Thread t;
+
+        public Thread newThread(Runnable r) {
+            t =  new Thread(r);
+            return t;
+        }
+
+        public void join() throws InterruptedException {
+            t.join();
+        }
+    }
+
+    private class VerifyingCallable implements Callable<SecurityContext> {
+
+        public SecurityContext call() throws Exception {
+            return SecurityContextHolder.getContext();
+        }
+
+    }
+}

+ 1 - 1
web/web.gradle

@@ -9,7 +9,7 @@ dependencies {
             "org.springframework:spring-tx:$springVersion",
             "org.springframework:spring-web:$springVersion"
 
-    provided 'javax.servlet:servlet-api:2.5'
+    provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
     testCompile project(':spring-security-core').sourceSets.test.output,
                 'commons-codec:commons-codec:1.3',