From e1d8db4e950ae8b461309c38b4f2532484270634 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Sun, 21 Jul 2013 22:56:23 -0500 Subject: [PATCH] SEC-2197: Allow multiple invocations on HttpSecurity Previously invoking methods like HttpSecurity#authorizeUrls() multiple times would override one another. This has now changed to be more intuitive. Initially this was required for the way that defaults were provided so that they could be overriden, but this is no longer the case. --- .../annotation/web/builders/HttpSecurity.java | 75 +++++++++++-------- .../AnonymousConfigurerTests.groovy | 54 +++++++++++++ .../ChannelSecurityConfigurerTests.groovy | 30 +++++++- .../ExceptionHandlingConfigurerTests.groovy | 24 ++++++ .../ExpressionUrlAuthorizationsTests.groovy | 32 ++++++++ .../FormLoginConfigurerTests.groovy | 24 ++++++ .../HttpBasicConfigurerTests.groovy | 33 +++++++- .../web/configurers/JeeConfigurerTests.groovy | 23 ++++++ .../configurers/LogoutConfigurerTests.groovy | 28 ++++++- .../PortMapperConfigurerTests.groovy | 65 ++++++++++++++++ .../RememberMeConfigurerTests.groovy | 15 ++++ .../RequestCacheConfigurerTests.groovy | 21 +++++- .../SecurityContextConfigurerTests.groovy | 32 +++++++- .../ServletApiConfigurerTests.groovy | 24 ++++++ .../SessionManagementConfigurerTests.groovy | 22 ++++++ .../configurers/X509ConfigurerTests.groovy | 17 +++++ .../openid/OpenIDLoginConfigurerTests.groovy | 41 +++++++++- 17 files changed, 516 insertions(+), 44 deletions(-) create mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.groovy create mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.groovy diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 4313d68e3a..1c2fd4efcc 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -29,6 +29,7 @@ import org.springframework.security.config.annotation.AbstractConfiguredSecurity import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityBuilder; import org.springframework.security.config.annotation.SecurityConfigurer; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; @@ -111,6 +112,7 @@ import org.springframework.util.Assert; public final class HttpSecurity extends AbstractConfiguredSecurityBuilder implements SecurityBuilder, HttpSecurityBuilder { private AuthenticationManager authenticationManager; + private final RequestMatcherConfigurer requestMatcherConfigurer = new RequestMatcherConfigurer(); private List filters = new ArrayList(); private RequestMatcher requestMatcher = new AnyRequestMatcher(); private FilterComparator comparitor = new FilterComparator(); @@ -132,8 +134,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilderExample Configurations * @@ -235,12 +236,11 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder openidLogin() throws Exception { - return apply(new OpenIDLoginConfigurer()); + return getOrApply(new OpenIDLoginConfigurer()); } /** - * Allows configuring of Session Management. Multiple invocations of - * {@link #sessionManagement()} will override previous invocations. + * Allows configuring of Session Management. * *

Example Configuration

* @@ -303,7 +303,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder sessionManagement() throws Exception { - return apply(new SessionManagementConfigurer()); + return getOrApply(new SessionManagementConfigurer()); } /** @@ -358,7 +358,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder portMapper() throws Exception { - return apply(new PortMapperConfigurer()); + return getOrApply(new PortMapperConfigurer()); } /** @@ -434,7 +434,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder jee() throws Exception { - return apply(new JeeConfigurer()); + return getOrApply(new JeeConfigurer()); } /** @@ -467,12 +467,11 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder x509() throws Exception { - return apply(new X509Configurer()); + return getOrApply(new X509Configurer()); } /** - * Allows configuring of Remember Me authentication. Multiple invocations of - * {@link #rememberMe()} will override previous invocations. + * Allows configuring of Remember Me authentication. * *

Example Configuration

* @@ -514,15 +513,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder rememberMe() throws Exception { - return apply(new RememberMeConfigurer()); + return getOrApply(new RememberMeConfigurer()); } /** * Allows restricting access based upon the {@link HttpServletRequest} using - * {@link RequestMatcher} implementations (i.e. via URL patterns). Invoking - * {@link #authorizeUrls()} twice will override previous invocations of - * {@link #authorizeUrls()}. * *

Example Configurations

* @@ -611,7 +607,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder authorizeUrls() throws Exception { - return apply(new ExpressionUrlAuthorizationConfigurer()); + return getOrApply(new ExpressionUrlAuthorizationConfigurer()); } /** @@ -624,7 +620,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder requestCache() throws Exception { - return apply(new RequestCacheConfigurer()); + return getOrApply(new RequestCacheConfigurer()); } /** @@ -635,7 +631,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder exceptionHandling() throws Exception { - return apply(new ExceptionHandlingConfigurer()); + return getOrApply(new ExceptionHandlingConfigurer()); } /** @@ -647,7 +643,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder securityContext() throws Exception { - return apply(new SecurityContextConfigurer()); + return getOrApply(new SecurityContextConfigurer()); } /** @@ -659,7 +655,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder servletApi() throws Exception { - return apply(new ServletApiConfigurer()); + return getOrApply(new ServletApiConfigurer()); } /** @@ -715,7 +711,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder logout() throws Exception { - return apply(new LogoutConfigurer()); + return getOrApply(new LogoutConfigurer()); } /** @@ -796,7 +792,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder anonymous() throws Exception { - return apply(new AnonymousConfigurer()); + return getOrApply(new AnonymousConfigurer()); } /** @@ -876,13 +872,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder formLogin() throws Exception { - return apply(new FormLoginConfigurer()); + return getOrApply(new FormLoginConfigurer()); } /** * Configures channel security. In order for this configuration to be useful at least - * one mapping to a required channel must be provided. Invoking this method multiple times - * will reset previous invocations of the method. + * one mapping to a required channel must be provided. * *

Example Configuration

* @@ -925,12 +920,11 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder requiresChannel() throws Exception { - return apply(new ChannelSecurityConfigurer()); + return getOrApply(new ChannelSecurityConfigurer()); } /** - * Configures HTTP Basic authentication. Multiple infocations of - * {@link #httpBasic()} will override previous invocations. + * Configures HTTP Basic authentication. * *

Example Configuration

* @@ -968,7 +962,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder httpBasic() throws Exception { - return apply(new HttpBasicConfigurer()); + return getOrApply(new HttpBasicConfigurer()); } @Override @@ -1115,9 +1109,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder * - * The configuration differs from the previous configurations because it invokes - * {@link #requestMatchers()} twice which resets the {@link RequestMatcherConfigurer}. - * Therefore the configuration below only matches on URLs that start with "/oauth/**". + * The configuration below is also the same as the above configuration. * *
      * @Configuration
@@ -1153,7 +1145,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder> C getOrApply(C configurer)
+            throws Exception {
+        C existingConfig = (C) getConfigurer(configurer.getClass());
+        if(existingConfig != null) {
+            return existingConfig;
+        }
+        return apply(configurer);
+    }
+
     /**
      * Internal {@link RequestMatcher} instance used by {@link RequestMatcher}
      * that will match if any of the passed in {@link RequestMatcher} instances
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.groovy
new file mode 100644
index 0000000000..3966efb3e4
--- /dev/null
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2013 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.annotation.web.configurers
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.AnyObjectPostProcessor
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter
+
+/**
+ *
+ * @author Rob Winch
+ */
+class AnonymousConfigurerTests extends BaseSpringSpec {
+
+    def "invoke logout twice does not override"() {
+        when:
+            loadConfig(InvokeTwiceDoesNotOverride)
+        then:
+            findFilter(AnonymousAuthenticationFilter).key == "custom"
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .anonymous()
+                    .key("custom")
+                    .and()
+                .anonymous()
+        }
+    }
+}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.groovy
index e46c22eca8..af5c945ba2 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.groovy
@@ -15,10 +15,13 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.context.annotation.Configuration
 import org.springframework.security.config.annotation.AnyObjectPostProcessor
 import org.springframework.security.config.annotation.BaseSpringSpec
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl
 import org.springframework.security.web.access.channel.ChannelProcessingFilter
 import org.springframework.security.web.access.channel.InsecureChannelProcessor
@@ -50,4 +53,27 @@ class ChannelSecurityConfigurerTests extends BaseSpringSpec {
         and: "ChannelProcessingFilter is registered with LifecycleManager"
             1 * objectPostProcessor.postProcess(_ as ChannelProcessingFilter) >> {ChannelProcessingFilter o -> o}
     }
+
+    def "invoke requiresChannel twice does not override"() {
+        setup:
+            loadConfig(DuplicateInvocationsDoesNotOverrideConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.redirectedUrl == "https://localhost"
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class DuplicateInvocationsDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .requiresChannel()
+                    .anyRequest().requiresSecure()
+                    .and()
+                .requiresChannel()
+        }
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy
index 24f76b5d00..c713277220 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy
@@ -29,6 +29,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.annotation.web.configurers.JeeConfigurerTests.InvokeTwiceDoesNotOverride;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.ExceptionTranslationFilter
 import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint
@@ -178,4 +179,27 @@ class ExceptionHandlingConfigurerTests extends BaseSpringSpec {
                 .formLogin()
         }
     }
+
+    def "invoke exceptionHandling twice does not override"() {
+        setup:
+            InvokeTwiceDoesNotOverrideConfig.AEP = Mock(AuthenticationEntryPoint)
+        when:
+            loadConfig(InvokeTwiceDoesNotOverrideConfig)
+        then:
+            findFilter(ExceptionTranslationFilter).authenticationEntryPoint == InvokeTwiceDoesNotOverrideConfig.AEP
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
+        static AuthenticationEntryPoint AEP
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .exceptionHandling()
+                    .authenticationEntryPoint(AEP)
+                    .and()
+                .exceptionHandling()
+        }
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy
index 6f29ef1a49..f58411b355 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy
@@ -411,4 +411,36 @@ public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec {
                     .withUser("user").password("password").roles("USER")
         }
     }
+
+
+    def "invoke authorizeUrls twice does not reset"() {
+        setup:
+            loadConfig(InvokeTwiceDoesNotResetConfig)
+        when:
+            request.method = "POST"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "Access is denied"
+            response.status == HttpServletResponse.SC_UNAUTHORIZED
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class InvokeTwiceDoesNotResetConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().authenticated()
+                    .and()
+                .authorizeUrls()
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+        }
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy
index 474b0345af..78bb4ba9cc 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy
@@ -196,6 +196,30 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
         }
     }
 
+    def "duplicate formLogin does not override"() {
+        setup:
+            DuplicateInvocationsDoesNotOverrideConfig.FAILURE_HANDLER = Mock(AuthenticationFailureHandler)
+        when:
+            loadConfig(DuplicateInvocationsDoesNotOverrideConfig)
+        then:
+            findFilter(UsernamePasswordAuthenticationFilter).usernameParameter == "custom-username"
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class DuplicateInvocationsDoesNotOverrideConfig extends BaseWebConfig {
+        static AuthenticationFailureHandler FAILURE_HANDLER
+
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .formLogin()
+                    .usernameParameter("custom-username")
+                    .and()
+                .formLogin()
+        }
+    }
+
     def "formLogin ObjectPostProcessor"() {
         setup: "initialize the AUTH_FILTER as a mock"
             AnyObjectPostProcessor opp = Mock()
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.groovy
index 50daac2eeb..7d4365c9ff 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.groovy
@@ -99,4 +99,35 @@ class HttpBasicConfigurerTests extends BaseSpringSpec {
                 .inMemoryAuthentication()
         }
     }
-}
+
+    def "duplicate httpBasic invocations does not override"() {
+        setup:
+            DuplicateDoesNotOverrideConfig.ENTRY_POINT = Mock(AuthenticationEntryPoint)
+        when:
+            loadConfig(DuplicateDoesNotOverrideConfig)
+        then:
+            findFilter(ExceptionTranslationFilter).authenticationEntryPoint == DuplicateDoesNotOverrideConfig.ENTRY_POINT
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class DuplicateDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
+        static AuthenticationEntryPoint ENTRY_POINT
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .authenticationEntryPoint(ENTRY_POINT)
+                    .and()
+                .httpBasic()
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+        }
+    }
+}
\ No newline at end of file
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.groovy
index aaa9bf5678..75dfe870c3 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.groovy
@@ -15,10 +15,13 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.AnyObjectPostProcessor
 import org.springframework.security.config.annotation.BaseSpringSpec
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
 import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter
 
@@ -43,4 +46,24 @@ class JeeConfigurerTests extends BaseSpringSpec {
         and: "J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource is registered with LifecycleManager"
             1 * opp.postProcess(_ as J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource) >> {J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource o -> o}
     }
+
+    def "invoke jee twice does not override"() {
+        when:
+            loadConfig(InvokeTwiceDoesNotOverride)
+        then:
+            findFilter(J2eePreAuthenticatedProcessingFilter).authenticationDetailsSource.j2eeMappableRoles == ["ROLE_USER"] as Set
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .jee()
+                    .mappableRoles("USER")
+                    .and()
+                .jee()
+        }
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy
index 7d7a2fe1b6..974e0e7fee 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy
@@ -15,10 +15,13 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.context.annotation.Configuration
 import org.springframework.security.config.annotation.AnyObjectPostProcessor
 import org.springframework.security.config.annotation.BaseSpringSpec
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.web.authentication.logout.LogoutFilter
 
 /**
@@ -40,4 +43,25 @@ class LogoutConfigurerTests extends BaseSpringSpec {
         then: "LogoutFilter is registered with LifecycleManager"
             1 * opp.postProcess(_ as LogoutFilter) >> {LogoutFilter o -> o}
     }
+
+    def "invoke logout twice does not override"() {
+        when:
+            loadConfig(InvokeTwiceDoesNotOverride)
+        then:
+            findFilter(LogoutFilter).filterProcessesUrl == "/custom/logout"
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .logout()
+                    .logoutUrl("/custom/logout")
+                    .and()
+                .logout()
+        }
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.groovy
new file mode 100644
index 0000000000..259aa555bb
--- /dev/null
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2013 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.annotation.web.configurers
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.AnyObjectPostProcessor
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.SessionCreationPolicy;
+import org.springframework.security.web.access.ExceptionTranslationFilter
+import org.springframework.security.web.context.NullSecurityContextRepository;
+import org.springframework.security.web.context.SecurityContextPersistenceFilter
+import org.springframework.security.web.context.SecurityContextRepository
+import org.springframework.security.web.savedrequest.RequestCache
+import org.springframework.security.web.session.ConcurrentSessionFilter
+import org.springframework.security.web.session.SessionManagementFilter
+
+/**
+ *
+ * @author Rob Winch
+ */
+class PortMapperConfigurerTests extends BaseSpringSpec {
+
+    def "invoke portMapper twice does not override"() {
+        setup:
+            loadConfig(InvokeTwiceDoesNotOverride)
+            request.setServerPort(543)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.redirectedUrl == "https://localhost:123"
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .requiresChannel()
+                    .anyRequest().requiresSecure()
+                    .and()
+                .portMapper()
+                    .http(543).mapsTo(123)
+                    .and()
+                .portMapper()
+        }
+    }
+}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy
index 63c5e9fb7e..b3d7f10707 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy
@@ -69,4 +69,19 @@ public class RememberMeConfigurerTests extends BaseSpringSpec {
         then: "RememberMeAuthenticationFilter is registered with LifecycleManager"
             1 * opp.postProcess(_ as RememberMeAuthenticationFilter) >> {RememberMeAuthenticationFilter o -> o}
     }
+
+    def "invoke rememberMe twice does not reset"() {
+        setup:
+            AnyObjectPostProcessor opp = Mock()
+            HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
+            UserDetailsService uds = authenticationBldr.getDefaultUserDetailsService()
+        when:
+            http
+                .rememberMe()
+                    .userDetailsService(authenticationBldr.getDefaultUserDetailsService())
+                    .and()
+                .rememberMe()
+        then: "RememberMeAuthenticationFilter is registered with LifecycleManager"
+            http.getConfigurer(RememberMeConfigurer).userDetailsService != null
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy
index 8e722f0bba..71e13b4108 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy
@@ -17,8 +17,9 @@ package org.springframework.security.config.annotation.web.configurers
 
 import org.springframework.security.config.annotation.AnyObjectPostProcessor
 import org.springframework.security.config.annotation.BaseSpringSpec
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.web.savedrequest.RequestCache
 import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
 
 /**
@@ -40,4 +41,20 @@ class RequestCacheConfigurerTests extends BaseSpringSpec {
         then: "RequestCacheAwareFilter is registered with LifecycleManager"
             1 * opp.postProcess(_ as RequestCacheAwareFilter) >> {RequestCacheAwareFilter o -> o}
     }
+
+    def "invoke requestCache twice does not reset"() {
+        setup:
+            RequestCache RC = Mock()
+            AnyObjectPostProcessor opp = Mock()
+            HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
+        when:
+            http
+                .requestCache()
+                    .requestCache(RC)
+                    .and()
+                .requestCache()
+
+        then:
+            http.getSharedObject(RequestCache) == RC
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.groovy
index 51a9c6af82..5234f605a2 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.groovy
@@ -15,11 +15,15 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.context.annotation.Configuration
 import org.springframework.security.config.annotation.AnyObjectPostProcessor
 import org.springframework.security.config.annotation.BaseSpringSpec
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.web.context.SecurityContextPersistenceFilter
+import org.springframework.security.web.context.SecurityContextRepository
 
 /**
  *
@@ -40,4 +44,28 @@ class SecurityContextConfigurerTests extends BaseSpringSpec {
         then: "SecurityContextPersistenceFilter is registered with LifecycleManager"
             1 * opp.postProcess(_ as SecurityContextPersistenceFilter) >> {SecurityContextPersistenceFilter o -> o}
     }
+
+    def "invoke securityContext twice does not override"() {
+        setup:
+            InvokeTwiceDoesNotOverrideConfig.SCR = Mock(SecurityContextRepository)
+        when:
+            loadConfig(InvokeTwiceDoesNotOverrideConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).repo == InvokeTwiceDoesNotOverrideConfig.SCR
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
+        static SecurityContextRepository SCR
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .securityContext()
+                    .securityContextRepository(SCR)
+                    .and()
+                .securityContext()
+        }
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy
index 3a795a3507..b402e1f521 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy
@@ -107,4 +107,28 @@ class ServletApiConfigurerTests extends BaseSpringSpec {
                     .withUser("user").password("password").roles("USER")
         }
     }
+
+    def "invoke servletApi twice does not override"() {
+        setup:
+            InvokeTwiceDoesNotOverrideConfig.ENTRYPOINT = Mock(AuthenticationEntryPoint)
+        when:
+            loadConfig(InvokeTwiceDoesNotOverrideConfig)
+        then:
+            findFilter(SecurityContextHolderAwareRequestFilter).authenticationEntryPoint == InvokeTwiceDoesNotOverrideConfig.ENTRYPOINT
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
+        static AuthenticationEntryPoint ENTRYPOINT
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .exceptionHandling()
+                    .authenticationEntryPoint(ENTRYPOINT)
+                    .and()
+                .exceptionHandling()
+        }
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy
index a9ec6ac3bd..5abed94d4b 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy
@@ -24,6 +24,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.annotation.web.configurers.SessionCreationPolicy;
 import org.springframework.security.web.access.ExceptionTranslationFilter
+import org.springframework.security.web.context.NullSecurityContextRepository;
 import org.springframework.security.web.context.SecurityContextPersistenceFilter
 import org.springframework.security.web.context.SecurityContextRepository
 import org.springframework.security.web.savedrequest.RequestCache
@@ -88,6 +89,27 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
 
     }
 
+    def "invoke sessionManagement twice does not override"() {
+        when:
+            loadConfig(InvokeTwiceDoesNotOverride)
+        then:
+            findFilter(SecurityContextPersistenceFilter).repo.class == NullSecurityContextRepository
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .sessionManagement()
+                    .sessionCreationPolicy(SessionCreationPolicy.stateless)
+                    .and()
+                .sessionManagement()
+        }
+
+    }
+
     def "sessionManagement ObjectPostProcessor"() {
         setup:
             AnyObjectPostProcessor opp = Mock()
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.groovy
index 4b8b9bc97a..f536a3f334 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.groovy
@@ -15,10 +15,13 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.AnyObjectPostProcessor
 import org.springframework.security.config.annotation.BaseSpringSpec
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
 
 /**
@@ -40,4 +43,18 @@ class X509ConfigurerTests extends BaseSpringSpec {
         then: "X509AuthenticationFilter is registered with LifecycleManager"
             1 * opp.postProcess(_ as X509AuthenticationFilter) >> {X509AuthenticationFilter o -> o}
     }
+
+    def "invoke x509 twice does not override"() {
+        setup:
+            AnyObjectPostProcessor opp = Mock()
+            HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
+        when:
+            http
+                .x509()
+                    .subjectPrincipalRegex(".*")
+                    .and()
+                .x509()
+        then:
+            http.getConfigurer(X509Configurer).subjectPrincipalRegex == ".*"
+    }
 }
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.groovy
index f16ea46ca7..0507f412a9 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.groovy
@@ -15,10 +15,13 @@
  */
 package org.springframework.security.config.annotation.web.configurers.openid
 
+import org.springframework.context.annotation.Configuration
 import org.springframework.security.config.annotation.AnyObjectPostProcessor
 import org.springframework.security.config.annotation.BaseSpringSpec
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper
 import org.springframework.security.core.userdetails.UserDetailsService
 import org.springframework.security.openid.OpenIDAuthenticationFilter
@@ -48,4 +51,38 @@ class OpenIDLoginConfigurerTests extends BaseSpringSpec {
         and: "OpenIDAuthenticationProvider is registered with LifecycleManager"
             1 * opp.postProcess(_ as OpenIDAuthenticationProvider) >> {OpenIDAuthenticationProvider o -> o}
     }
+
+    def "invoke openidLogin twice does not override"() {
+        setup:
+            loadConfig(InvokeTwiceDoesNotOverrideConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.redirectedUrl.endsWith("/login/custom")
+
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+        }
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .anyRequest().authenticated()
+                    .and()
+                .openidLogin()
+                    .loginPage("/login/custom")
+                    .and()
+                .openidLogin()
+        }
+    }
 }