Prechádzať zdrojové kódy

SEC-1953: Spring Security Java Config support

This is the initial migration of Spring Security Java Config from the
external project at
https://github.com/SpringSource/spring-security-javaconfig
Rob Winch 12 rokov pred
rodič
commit
d0c4e6ca72
100 zmenil súbory, kde vykonal 16046 pridanie a 1 odobranie
  1. 12 1
      config/config.gradle
  2. 177 0
      config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.groovy
  3. 68 0
      config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTests.groovy
  4. 84 0
      config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTestsConfigs.java
  5. 42 0
      config/src/integration-test/resources/users.ldif
  6. 432 0
      config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java
  7. 67 0
      config/src/main/java/org/springframework/security/config/annotation/AbstractSecurityBuilder.java
  8. 52 0
      config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java
  9. 35 0
      config/src/main/java/org/springframework/security/config/annotation/SecurityBuilder.java
  10. 55 0
      config/src/main/java/org/springframework/security/config/annotation/SecurityConfigurer.java
  11. 135 0
      config/src/main/java/org/springframework/security/config/annotation/SecurityConfigurerAdapter.java
  12. 44 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/ProviderManagerBuilder.java
  13. 254 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java
  14. 469 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java
  15. 42 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/InMemoryUserDetailsManagerConfigurer.java
  16. 192 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/JdbcUserDetailsManagerConfigurer.java
  17. 263 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/UserDetailsManagerConfigurer.java
  18. 95 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/AbstractDaoAuthenticationConfigurer.java
  19. 41 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/DaoAuthenticationConfigurer.java
  20. 39 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/UserDetailsAwareConfigurer.java
  21. 58 0
      config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/UserDetailsServiceConfigurer.java
  22. 77 0
      config/src/main/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java
  23. 44 0
      config/src/main/java/org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.java
  24. 101 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.java
  25. 68 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityAspectJAutoProxyRegistrar.java
  26. 385 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java
  27. 59 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecuritySelector.java
  28. 198 0
      config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherConfigurer.java
  29. 177 0
      config/src/main/java/org/springframework/security/config/annotation/web/HttpSecurityBuilder.java
  30. 41 0
      config/src/main/java/org/springframework/security/config/annotation/web/WebSecurityConfigurer.java
  31. 191 0
      config/src/main/java/org/springframework/security/config/annotation/web/builders/DebugFilter.java
  32. 173 0
      config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java
  33. 1280 0
      config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
  34. 309 0
      config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java
  35. 87 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java
  36. 187 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java
  37. 326 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
  38. 319 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java
  39. 33 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java
  40. 181 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractInterceptUrlConfigurer.java
  41. 144 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurer.java
  42. 165 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java
  43. 168 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java
  44. 82 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurer.java
  45. 164 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java
  46. 296 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.java
  47. 239 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java
  48. 127 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java
  49. 261 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurer.java
  50. 243 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java
  51. 73 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java
  52. 115 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurer.java
  53. 352 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurer.java
  54. 98 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java
  55. 95 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurer.java
  56. 75 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java
  57. 39 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionCreationPolicy.java
  58. 331 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
  59. 241 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java
  60. 196 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java
  61. 463 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java
  62. 25 0
      config/src/test/groovy/org/springframework/security/config/annotation/AnyObjectPostProcessor.java
  63. 122 0
      config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy
  64. 52 0
      config/src/test/groovy/org/springframework/security/config/annotation/BaseWebSpecuritySpec.groovy
  65. 37 0
      config/src/test/groovy/org/springframework/security/config/annotation/ConcereteSecurityConfigurerAdapter.java
  66. 50 0
      config/src/test/groovy/org/springframework/security/config/annotation/ObjectPostProcessorTests.java
  67. 40 0
      config/src/test/groovy/org/springframework/security/config/annotation/SecurityConfigurerAdapterTests.groovy
  68. 90 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy
  69. 50 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/BaseAuthenticationConfig.groovy
  70. 92 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespaceAuthenticationManagerTests.groovy
  71. 83 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespaceAuthenticationProviderTests.groovy
  72. 188 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespaceJdbcUserServiceTests.groovy
  73. 126 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespacePasswordEncoderTests.groovy
  74. 83 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/PasswordEncoderConfigurerConfigs.java
  75. 61 0
      config/src/test/groovy/org/springframework/security/config/annotation/authentication/PasswordEncoderConfigurerTests.groovy
  76. 120 0
      config/src/test/groovy/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessorTests.groovy
  77. 85 0
      config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfigurationTests.groovy
  78. 51 0
      config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.groovy
  79. 62 0
      config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.groovy
  80. 93 0
      config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.groovy
  81. 443 0
      config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.groovy
  82. 129 0
      config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/SampleEnableGlobalMethodSecurityTests.groovy
  83. 97 0
      config/src/test/groovy/org/springframework/security/config/annotation/provisioning/UserDetailsManagerConfigurerTests.groovy
  84. 171 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/AbstractConfiguredSecurityBuilderTests.groovy
  85. 59 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/RequestMatchersTests.groovy
  86. 321 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy
  87. 102 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy
  88. 76 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTestsConfigs.java
  89. 120 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/builders/HttpConfigurationTests.groovy
  90. 511 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.groovy
  91. 42 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configuration/BaseWebConfig.groovy
  92. 88 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configuration/EnableWebSecurityTests.groovy
  93. 217 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.groovy
  94. 76 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurerTests.groovy
  95. 53 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.groovy
  96. 159 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy
  97. 343 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy
  98. 43 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy
  99. 413 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy
  100. 214 0
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy

+ 12 - 1
config/config.gradle

@@ -15,12 +15,22 @@ dependencies {
     // NB: Don't add other compile time dependencies to the config module as this breaks tooling
     compile project(':spring-security-core'),
             project(':spring-security-web'),
+            project(':spring-security-openid'),
+            project(':spring-security-ldap'),
             "org.aspectj:aspectjweaver:$aspectjVersion",
             'aopalliance:aopalliance:1.0',
             "org.springframework:spring-aop:$springVersion",
             "org.springframework:spring-context:$springVersion",
             "org.springframework:spring-web:$springVersion",
-            "org.springframework:spring-beans:$springVersion"
+            "org.springframework:spring-beans:$springVersion",
+            "org.springframework:spring-jdbc:$springVersion",
+            "org.springframework:spring-tx:$springVersion",
+            "org.springframework.ldap:spring-ldap-core:$springLdapVersion"
+            compile('org.openid4java:openid4java-nodeps:0.9.6') {
+                exclude group: 'com.google.code.guice', module: 'guice'
+            }
+            compile 'com.google.inject:guice:2.0'
+            compile apacheds_libs
 
     provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
 
@@ -28,6 +38,7 @@ dependencies {
 
     testCompile project(':spring-security-ldap'),
                 project(':spring-security-openid'),
+                project(':spring-security-cas'),
                 project(':spring-security-core').sourceSets.test.output,
                 'javax.annotation:jsr250-api:1.0',
                 "org.springframework.ldap:spring-ldap-core:$springLdapVersion",

+ 177 - 0
config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.groovy

@@ -0,0 +1,177 @@
+/*
+ * 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.authentication.ldap
+
+import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.ldap.core.ContextSource;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.AuthenticationProvider
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
+import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
+import org.springframework.security.ldap.server.ApacheDSContainer;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class LdapAuthenticationProviderBuilderSecurityBuilderTests extends BaseSpringSpec {
+    def "default configuration"() {
+        when:
+        loadConfig(DefaultLdapConfig)
+        LdapAuthenticationProvider provider = ldapProvider()
+        then:
+        provider.authoritiesPopulator.groupRoleAttribute == "cn"
+        provider.authoritiesPopulator.groupSearchBase == ""
+        provider.authoritiesPopulator.groupSearchFilter == "(uniqueMember={0})"
+        ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "ROLE_"
+
+    }
+
+    @Configuration
+    static class DefaultLdapConfig extends BaseLdapProviderConfig {
+        protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .contextSource(contextSource())
+        }
+    }
+
+    def "group roles custom"() {
+        when:
+        loadConfig(GroupRolesConfig)
+        LdapAuthenticationProvider provider = ldapProvider()
+        then:
+        provider.authoritiesPopulator.groupRoleAttribute == "group"
+    }
+
+    @Configuration
+    static class GroupRolesConfig extends BaseLdapProviderConfig {
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .contextSource(contextSource())
+                    .groupRoleAttribute("group")
+        }
+    }
+
+    def "group search custom"() {
+        when:
+        loadConfig(GroupSearchConfig)
+        LdapAuthenticationProvider provider = ldapProvider()
+        then:
+        provider.authoritiesPopulator.groupSearchFilter == "ou=groupName"
+    }
+
+    @Configuration
+    static class GroupSearchConfig extends BaseLdapProviderConfig {
+        protected void registerAuthentication(
+            AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .contextSource(contextSource())
+                    .groupSearchFilter("ou=groupName");
+        }
+    }
+
+    def "role prefix custom"() {
+        when:
+        loadConfig(RolePrefixConfig)
+        LdapAuthenticationProvider provider = ldapProvider()
+        then:
+        ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "role_"
+    }
+
+    @Configuration
+    static class RolePrefixConfig extends BaseLdapProviderConfig {
+        protected void registerAuthentication(
+            AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .contextSource(contextSource())
+                    .rolePrefix("role_")
+        }
+    }
+
+    def "bind authentication"() {
+        when:
+        loadConfig(BindAuthenticationConfig)
+        AuthenticationManager auth = context.getBean(AuthenticationManager)
+        then:
+        auth
+        auth.authenticate(new UsernamePasswordAuthenticationToken("admin","password")).authorities.collect { it.authority }.sort() == ["ROLE_ADMIN","ROLE_USER"]
+    }
+
+    @Configuration
+    static class BindAuthenticationConfig extends BaseLdapServerConfig {
+        protected void registerAuthentication(
+            AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .contextSource(contextSource())
+                    .groupSearchBase("ou=groups")
+                    .userDnPatterns("uid={0},ou=people");
+        }
+    }
+
+    def ldapProvider() {
+        context.getBean(AuthenticationManager).providers[0]
+    }
+
+    @Configuration
+    static abstract class BaseLdapServerConfig extends BaseLdapProviderConfig {
+        @Bean
+        public ApacheDSContainer ldapServer() throws Exception {
+            ApacheDSContainer apacheDSContainer = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/users.ldif");
+            apacheDSContainer.setPort(33389);
+            return apacheDSContainer;
+        }
+    }
+
+    @Configuration
+    static abstract class BaseLdapProviderConfig {
+        @Bean
+        public AuthenticationManager authenticationManager() {
+            AuthenticationManagerBuilder registry = new AuthenticationManagerBuilder();
+            registerAuthentication(registry);
+            return registry.build();
+        }
+
+        protected abstract void registerAuthentication(
+            AuthenticationManagerBuilder auth) throws Exception;
+
+        @Bean
+        public BaseLdapPathContextSource contextSource() throws Exception {
+            DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
+                    "ldap://127.0.0.1:33389/dc=springframework,dc=org")
+            contextSource.userDn = "uid=admin,ou=system"
+            contextSource.password = "secret"
+            contextSource.afterPropertiesSet();
+            return contextSource;
+        }
+    }
+}

+ 68 - 0
config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTests.groovy

@@ -0,0 +1,68 @@
+/*
+ * 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.authentication.ldap
+
+import static org.springframework.security.config.annotation.authentication.ldap.NamespaceLdapAuthenticationProviderTestsConfigs.*
+
+import org.springframework.ldap.core.support.BaseLdapPathContextSource
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.AuthenticationProvider
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.ldap.NamespaceLdapAuthenticationProviderTestsConfigs.LdapAuthenticationProviderConfig;
+import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
+import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
+import org.springframework.security.ldap.userdetails.PersonContextMapper;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class NamespaceLdapAuthenticationProviderTests extends BaseSpringSpec {
+    def "ldap-authentication-provider"() {
+        when:
+            loadConfig(LdapAuthenticationProviderConfig)
+        then:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password")).authorities*.authority.sort() == ['ROLE_USER']
+    }
+
+    def "ldap-authentication-provider custom"() {
+        when:
+            loadConfig(CustomLdapAuthenticationProviderConfig)
+            LdapAuthenticationProvider provider = findAuthenticationProvider(LdapAuthenticationProvider)
+        then:
+            provider.authoritiesPopulator.groupRoleAttribute == "cn"
+            provider.authoritiesPopulator.groupSearchBase == "ou=groups"
+            provider.authoritiesPopulator.groupSearchFilter == "(member={0})"
+            ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "PREFIX_"
+            provider.userDetailsContextMapper instanceof PersonContextMapper
+            provider.authenticator.getUserDns("user") == ["uid=user,ou=people"]
+            provider.authenticator.userSearch.searchBase == "ou=users"
+            provider.authenticator.userSearch.searchFilter == "(uid={0})"
+    }
+
+    def "ldap-authentication-provider password compare"() {
+        when:
+            loadConfig(PasswordCompareLdapConfig)
+            LdapAuthenticationProvider provider = findAuthenticationProvider(LdapAuthenticationProvider)
+        then:
+            provider.authenticator instanceof PasswordComparisonAuthenticator
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password")).authorities*.authority.sort() == ['ROLE_USER']
+    }
+}

+ 84 - 0
config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTestsConfigs.java

@@ -0,0 +1,84 @@
+/*
+ * 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.authentication.ldap;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.ldap.userdetails.PersonContextMapper;
+
+/**
+ * @author Rob Winch
+ *
+ */
+public class NamespaceLdapAuthenticationProviderTestsConfigs {
+
+    @Configuration
+    @EnableWebSecurity
+    static class LdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .groupSearchBase("ou=groups")
+                    .userDnPatterns("uid={0},ou=people"); // ldap-server@user-dn-pattern
+        }
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class CustomLdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .groupRoleAttribute("cn") // ldap-authentication-provider@group-role-attribute
+                    .groupSearchBase("ou=groups") // ldap-authentication-provider@group-search-base
+                    .groupSearchFilter("(member={0})") // ldap-authentication-provider@group-search-filter
+                    .rolePrefix("PREFIX_") // ldap-authentication-provider@group-search-filter
+                    .userDetailsContextMapper(new PersonContextMapper()) // ldap-authentication-provider@user-context-mapper-ref / ldap-authentication-provider@user-details-class
+                    .userDnPatterns("uid={0},ou=people") // ldap-authentication-provider@user-dn-pattern
+                    .userSearchBase("ou=users") // ldap-authentication-provider@user-dn-pattern
+                    .userSearchFilter("(uid={0})") // ldap-authentication-provider@user-search-filter
+                    // .contextSource(contextSource) // ldap-authentication-provider@server-ref
+                    .contextSource()
+                        .ldif("classpath:user.ldif") // ldap-server@ldif
+                        .managerDn("uid=admin,ou=system") // ldap-server@manager-dn
+                        .managerPassword("secret") // ldap-server@manager-password
+                        .port(33399) // ldap-server@port
+                        .root("dc=springframework,dc=org") // ldap-server@root
+                        // .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
+                        ;
+        }
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class PasswordCompareLdapConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .ldapAuthentication()
+                    .groupSearchBase("ou=groups")
+                    .userSearchFilter("(uid={0})")
+                    .passwordCompare()
+                        .passwordEncoder(new PlaintextPasswordEncoder()) // ldap-authentication-provider/password-compare/password-encoder@ref
+                        .passwordAttribute("userPassword"); // ldap-authentication-provider/password-compare@password-attribute
+        }
+    }
+}

+ 42 - 0
config/src/integration-test/resources/users.ldif

@@ -0,0 +1,42 @@
+dn: ou=groups,dc=springframework,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: groups
+
+dn: ou=people,dc=springframework,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: people
+
+dn: uid=admin,ou=people,dc=springframework,dc=org
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+cn: Rod Johnson
+sn: Johnson
+uid: admin
+userPassword: password
+
+dn: uid=user,ou=people,dc=springframework,dc=org
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+cn: Dianne Emu
+sn: Emu
+uid: user
+userPassword: password
+
+dn: cn=user,ou=groups,dc=springframework,dc=org
+objectclass: top
+objectclass: groupOfNames
+cn: user
+uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
+uniqueMember: uid=user,ou=people,dc=springframework,dc=org
+
+dn: cn=admin,ou=groups,dc=springframework,dc=org
+objectclass: top
+objectclass: groupOfNames
+cn: admin
+uniqueMember: uid=admin,ou=people,dc=springframework,dc=org

+ 432 - 0
config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java

@@ -0,0 +1,432 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.util.Assert;
+import org.springframework.web.filter.DelegatingFilterProxy;
+
+import com.google.inject.internal.ImmutableList.Builder;
+
+/**
+ * <p>A base {@link SecurityBuilder} that allows {@link SecurityConfigurer} to be
+ * applied to it. This makes modifying the {@link SecurityBuilder} a strategy
+ * that can be customized and broken up into a number of
+ * {@link SecurityConfigurer} objects that have more specific goals than that
+ * of the {@link SecurityBuilder}.</p>
+ *
+ * <p>For example, a {@link SecurityBuilder} may build an
+ * {@link DelegatingFilterProxy}, but a {@link SecurityConfigurer} might
+ * populate the {@link SecurityBuilder} with the filters necessary for session
+ * management, form based login, authorization, etc.</p>
+ *
+ * @see WebSecurity
+ *
+ * @author Rob Winch
+ *
+ * @param <O>
+ *            The object that this builder returns
+ * @param <B>
+ *            The type of this builder (that is returned by the base class)
+ */
+public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> {
+
+    private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers =
+            new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
+
+    private final Map<Class<Object>,Object> sharedObjects = new HashMap<Class<Object>,Object>();
+
+    private final boolean allowConfigurersOfSameType;
+
+    private BuildState buildState = BuildState.UNBUILT;
+
+    private ObjectPostProcessor<Object> objectPostProcessor;
+
+    /**
+     * Creates a new instance without post processing
+     */
+    protected AbstractConfiguredSecurityBuilder() {
+        this(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR);
+    }
+
+    /***
+     * Creates a new instance with the provided {@link ObjectPostProcessor}.
+     * This post processor must support Object since there are many types of
+     * objects that may be post processed.
+     *
+     * @param objectPostProcessor the {@link ObjectPostProcessor} to use
+     */
+    protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
+        this(objectPostProcessor,false);
+    }
+
+    /***
+     * Creates a new instance with the provided {@link ObjectPostProcessor}.
+     * This post processor must support Object since there are many types of
+     * objects that may be post processed.
+     *
+     * @param objectPostProcessor the {@link ObjectPostProcessor} to use
+     * @param allowConfigurersOfSameType if true, will not override other {@link SecurityConfigurer}'s when performing apply
+     */
+    protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor, boolean allowConfigurersOfSameType) {
+        Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
+        this.objectPostProcessor = objectPostProcessor;
+        this.allowConfigurersOfSameType = allowConfigurersOfSameType;
+    }
+
+    /**
+     * Applies a {@link SecurityConfigurerAdapter} to this
+     * {@link SecurityBuilder} and invokes
+     * {@link SecurityConfigurerAdapter#setBuilder(SecurityBuilder)}.
+     *
+     * @param configurer
+     * @return
+     * @throws Exception
+     */
+    @SuppressWarnings("unchecked")
+    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
+            throws Exception {
+        add(configurer);
+        configurer.addObjectPostProcessor(objectPostProcessor);
+        configurer.setBuilder((B) this);
+        return configurer;
+    }
+
+    /**
+     * Applies a {@link SecurityConfigurer} to this {@link SecurityBuilder}
+     * overriding any {@link SecurityConfigurer} of the exact same class. Note
+     * that object hierarchies are not considered.
+     *
+     * @param configurer
+     * @return
+     * @throws Exception
+     */
+    public <C extends SecurityConfigurer<O, B>> C apply(C configurer)
+            throws Exception {
+        add(configurer);
+        return configurer;
+    }
+
+    /**
+     * Sets an object that is shared by multiple {@link SecurityConfigurer}.
+     *
+     * @param sharedType the Class to key the shared object by.
+     * @param object the Object to store
+     */
+    @SuppressWarnings("unchecked")
+    public <C> void setSharedObject(Class<C> sharedType, C object) {
+        this.sharedObjects.put((Class<Object>) sharedType, object);
+    }
+
+    /**
+     * Gets a shared Object. Note that object heirarchies are not considered.
+     *
+     * @param sharedType the type of the shared Object
+     * @return the shared Object or null if it is not found
+     */
+    @SuppressWarnings("unchecked")
+    public <C> C getSharedObject(Class<C> sharedType) {
+        return (C) this.sharedObjects.get(sharedType);
+    }
+
+    /**
+     * Gets the shared objects
+     * @return
+     */
+    public Map<Class<Object>,Object> getSharedObjects() {
+        return Collections.unmodifiableMap(this.sharedObjects);
+    }
+
+    /**
+     * Adds {@link SecurityConfigurer} ensuring that it is allowed and
+     * invoking {@link SecurityConfigurer#init(SecurityBuilder)} immediately
+     * if necessary.
+     *
+     * @param configurer the {@link SecurityConfigurer} to add
+     * @throws Exception if an error occurs
+     */
+    @SuppressWarnings("unchecked")
+    private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
+        Assert.notNull(configurer, "configurer cannot be null");
+
+        Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
+                .getClass();
+        synchronized(configurers) {
+            if(buildState.isConfigured()) {
+                throw new IllegalStateException("Cannot apply "+configurer+" to already built object");
+            }
+            List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers.get(clazz) : null;
+            if(configs == null) {
+                configs = new ArrayList<SecurityConfigurer<O,B>>(1);
+            }
+            configs.add(configurer);
+            this.configurers.put(clazz, configs);
+            if(buildState.isInitializing()) {
+                configurer.init((B)this);
+            }
+        }
+    }
+
+    /**
+     * Gets all the {@link SecurityConfigurer} instances by its class name or an
+     * empty List if not found. Note that object hierarchies are not considered.
+     *
+     * @param clazz the {@link SecurityConfigurer} class to look for
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(
+            Class<C> clazz) {
+        List<C> configs = (List<C>) this.configurers.get(clazz);
+        if(configs == null) {
+            return new ArrayList<C>();
+        }
+        return new ArrayList<C>(configs);
+    }
+
+    /**
+     * Removes all the {@link SecurityConfigurer} instances by its class name or an
+     * empty List if not found. Note that object hierarchies are not considered.
+     *
+     * @param clazz the {@link SecurityConfigurer} class to look for
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(
+            Class<C> clazz) {
+        List<C> configs = (List<C>) this.configurers.remove(clazz);
+        if(configs == null) {
+            return new ArrayList<C>();
+        }
+        return new ArrayList<C>(configs);
+    }
+
+    /**
+     * Gets the {@link SecurityConfigurer} by its class name or
+     * <code>null</code> if not found. Note that object hierarchies are not
+     * considered.
+     *
+     * @param clazz
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public <C extends SecurityConfigurer<O, B>> C getConfigurer(
+            Class<C> clazz) {
+        List<SecurityConfigurer<O,B>> configs = this.configurers.get(clazz);
+        if(configs == null) {
+            return null;
+        }
+        if(configs.size() != 1) {
+            throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
+        }
+        return (C) configs.get(0);
+    }
+
+    /**
+     * Removes and returns the {@link SecurityConfigurer} by its class name or
+     * <code>null</code> if not found. Note that object hierarchies are not
+     * considered.
+     *
+     * @param clazz
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public <C extends SecurityConfigurer<O,B>> C removeConfigurer(Class<C> clazz) {
+        List<SecurityConfigurer<O,B>> configs = this.configurers.remove(clazz);
+        if(configs == null) {
+            return null;
+        }
+        if(configs.size() != 1) {
+            throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
+        }
+        return (C) configs.get(0);
+    }
+
+    /**
+     * Specifies the {@link ObjectPostProcessor} to use.
+     * @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null
+     * @return the {@link SecurityBuilder} for further customizations
+     */
+    @SuppressWarnings("unchecked")
+    public O objectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
+        Assert.notNull(objectPostProcessor,"objectPostProcessor cannot be null");
+        this.objectPostProcessor = objectPostProcessor;
+        return (O) this;
+    }
+
+    /**
+     * Performs post processing of an object. The default is to delegate to the
+     * {@link ObjectPostProcessor}.
+     *
+     * @param object the Object to post process
+     * @return the possibly modified Object to use
+     */
+    protected <P> P postProcess(P object) {
+        return (P) this.objectPostProcessor.postProcess(object);
+    }
+
+    /**
+     * Executes the build using the {@link SecurityConfigurer}'s that have been applied using the following steps:
+     *
+     * <ul>
+     * <li>Invokes {@link #beforeInit()} for any subclass to hook into</li>
+     * <li>Invokes {@link SecurityConfigurer#init(SecurityBuilder)} for any {@link SecurityConfigurer} that was applied to this builder.</li>
+     * <li>Invokes {@link #beforeConfigure()} for any subclass to hook into</li>
+     * <li>Invokes {@link #performBuild()} which actually builds the Object</li>
+     * </ul>
+     */
+    @Override
+    protected final O doBuild() throws Exception {
+        synchronized(configurers) {
+            buildState = BuildState.INITIALIZING;
+
+            beforeInit();
+            init();
+
+            buildState = BuildState.CONFIGURING;
+
+            beforeConfigure();
+            configure();
+
+            buildState = BuildState.BUILDING;
+
+            O result = performBuild();
+
+            buildState = BuildState.BUILT;
+
+            return result;
+        }
+    }
+
+    /**
+     * Invoked prior to invoking each
+     * {@link SecurityConfigurer#init(SecurityBuilder)} method. Subclasses may
+     * override this method to hook into the lifecycle without using a
+     * {@link SecurityConfigurer}.
+     */
+    protected void beforeInit() throws Exception {
+    }
+
+    /**
+     * Invoked prior to invoking each
+     * {@link SecurityConfigurer#configure(SecurityBuilder)} method.
+     * Subclasses may override this method to hook into the lifecycle without
+     * using a {@link SecurityConfigurer}.
+     */
+    protected void beforeConfigure() throws Exception {
+    }
+
+    /**
+     * Subclasses must implement this method to build the object that is being returned.
+     *
+     * @return
+     */
+    protected abstract O performBuild() throws Exception;
+
+    @SuppressWarnings("unchecked")
+    private void init() throws Exception {
+        Collection<SecurityConfigurer<O,B>> configurers = getConfigurers();
+
+        for(SecurityConfigurer<O,B> configurer : configurers ) {
+            configurer.init((B) this);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void configure() throws Exception {
+        Collection<SecurityConfigurer<O,B>> configurers = getConfigurers();
+
+        for(SecurityConfigurer<O,B> configurer : configurers ) {
+            configurer.configure((B) this);
+        }
+    }
+
+    private Collection<SecurityConfigurer<O, B>> getConfigurers() {
+        List<SecurityConfigurer<O,B>> result = new ArrayList<SecurityConfigurer<O,B>>();
+        for(List<SecurityConfigurer<O,B>> configs : this.configurers.values()) {
+            result.addAll(configs);
+        }
+        return result;
+    }
+
+    /**
+     * The build state for the application
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    private static enum BuildState {
+        /**
+         * This is the state before the {@link Builder#build()} is invoked
+         */
+        UNBUILT(0),
+
+        /**
+         * The state from when {@link Builder#build()} is first invoked until
+         * all the {@link SecurityConfigurer#init(SecurityBuilder)} methods
+         * have been invoked.
+         */
+        INITIALIZING(1),
+
+        /**
+         * The state from after all
+         * {@link SecurityConfigurer#init(SecurityBuilder)} have been invoked
+         * until after all the
+         * {@link SecurityConfigurer#configure(SecurityBuilder)} methods have
+         * been invoked.
+         */
+        CONFIGURING(2),
+
+        /**
+         * From the point after all the
+         * {@link SecurityConfigurer#configure(SecurityBuilder)} have
+         * completed to just after
+         * {@link AbstractConfiguredSecurityBuilder#performBuild()}.
+         */
+        BUILDING(3),
+
+        /**
+         * After the object has been completely built.
+         */
+        BUILT(4);
+
+        private final int order;
+
+        BuildState(int order) {
+            this.order = order;
+        }
+
+        public boolean isInitializing() {
+            return INITIALIZING.order == order;
+        }
+
+        /**
+         * Determines if the state is CONFIGURING or later
+         * @return
+         */
+        public boolean isConfigured() {
+            return order >= CONFIGURING.order;
+        }
+    }
+}

+ 67 - 0
config/src/main/java/org/springframework/security/config/annotation/AbstractSecurityBuilder.java

@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A base {@link SecurityBuilder} that ensures the object being built is only
+ * built one time.
+ * 
+ * @param <O> the type of Object that is being built
+ *
+ * @author Rob Winch
+ *
+ */
+public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
+    private AtomicBoolean building = new AtomicBoolean();
+
+    private O object;
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.SecurityBuilder#build()
+     */
+    @Override
+    public final O build() throws Exception {
+        if(building.compareAndSet(false, true)) {
+            object = doBuild();
+            return object;
+        }
+        throw new IllegalStateException("This object has already been built");
+    }
+
+    /**
+     * Gets the object that was built. If it has not been built yet an Exception
+     * is thrown.
+     *
+     * @return the Object that was built
+     */
+    public final O getObject() {
+        if(!building.get()) {
+            throw new IllegalStateException("This object has not been built");
+        }
+        return object;
+    }
+
+    /**
+     * Subclasses should implement this to perform the build.
+     *
+     * @return the object that should be returned by {@link #build()}.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected abstract O doBuild() throws Exception;
+}

+ 52 - 0
config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java

@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import org.springframework.beans.factory.Aware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * Allows initialization of Objects. Typically this is used to call the
+ * {@link Aware} methods, {@link InitializingBean#afterPropertiesSet()}, and
+ * ensure that {@link DisposableBean#destroy()} has been invoked.
+ *
+ * @param <T> the bound of the types of Objects this {@link ObjectPostProcessor} supports.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public interface ObjectPostProcessor<T> {
+
+    /**
+     * Initialize the object possibly returning a modified instance that should
+     * be used instead.
+     *
+     * @param object the object to initialize
+     * @return the initialized version of the object
+     */
+    <O extends T> O postProcess(O object);
+
+    /**
+     * A do nothing implementation of the {@link ObjectPostProcessor}
+     */
+    ObjectPostProcessor<Object> QUIESCENT_POSTPROCESSOR =  new ObjectPostProcessor<Object>() {
+        @Override
+        public <T> T postProcess(T object) {
+            return object;
+        }
+    };
+}

+ 35 - 0
config/src/main/java/org/springframework/security/config/annotation/SecurityBuilder.java

@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Interface for building an Object
+ *
+ * @author Rob Winch
+ * @since 3.2
+ *
+ * @param <O> The type of the Object being built
+ */
+public interface SecurityBuilder<O> {
+
+    /**
+     * Builds the object and returns it or null.
+     *
+     * @return the Object to be built or null if the implementation allows it.
+     * @throws Exception if an error occured when building the Object
+     */
+    O build() throws Exception;
+}

+ 55 - 0
config/src/main/java/org/springframework/security/config/annotation/SecurityConfigurer.java

@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+/**
+ * Allows for configuring a {@link SecurityBuilder}. All
+ * {@link SecurityConfigurer} first have their {@link #init(SecurityBuilder)}
+ * method invoked. After all {@link #init(SecurityBuilder)} methods have been
+ * invoked, each {@link #configure(SecurityBuilder)} method is invoked.
+ *
+ * @see AbstractConfiguredSecurityBuilder
+ *
+ * @author Rob Winch
+ *
+ * @param <O>
+ *            The object being built by the {@link SecurityBuilder} B
+ * @param <B>
+ *            The {@link SecurityBuilder} that builds objects of type O. This is
+ *            also the {@link SecurityBuilder} that is being configured.
+ */
+public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
+    /**
+     * Initialize the {@link SecurityBuilder}. Here only shared state should be
+     * created and modified, but not properties on the {@link SecurityBuilder}
+     * used for building the object. This ensures that the
+     * {@link #configure(SecurityBuilder)} method uses the correct shared
+     * objects when building.
+     *
+     * @param builder
+     * @throws Exception
+     */
+    void init(B builder) throws Exception;
+
+    /**
+     * Configure the {@link SecurityBuilder} by setting the necessary properties
+     * on the {@link SecurityBuilder}.
+     *
+     * @param builder
+     * @throws Exception
+     */
+    void configure(B builder) throws Exception;
+}

+ 135 - 0
config/src/main/java/org/springframework/security/config/annotation/SecurityConfigurerAdapter.java

@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.core.GenericTypeResolver;
+
+/**
+ * A base class for {@link SecurityConfigurer} that allows subclasses to only
+ * implement the methods they are interested in. It also provides a mechanism
+ * for using the {@link SecurityConfigurer} and when done gaining access to the
+ * {@link SecurityBuilder} that is being configured.
+ *
+ * @author Rob Winch
+ *
+ * @param <O>
+ *            The Object being built by B
+ * @param <B>
+ *            The Builder that is building O and is configured by {@link SecurityConfigurerAdapter}
+ */
+public abstract class SecurityConfigurerAdapter<O,B extends SecurityBuilder<O>> implements SecurityConfigurer<O,B> {
+    private B securityBuilder;
+
+    private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();
+
+    @Override
+    public void init(B builder) throws Exception {}
+
+    @Override
+    public void configure(B builder) throws Exception {}
+
+    /**
+     * Return the {@link SecurityBuilder} when done using the
+     * {@link SecurityConfigurer}. This is useful for method chaining.
+     *
+     * @return
+     */
+    public B and() {
+        return getBuilder();
+    }
+
+    /**
+     * Gets the {@link SecurityBuilder}. Cannot be null.
+     *
+     * @return the {@link SecurityBuilder}
+     * @throw {@link IllegalStateException} if {@link SecurityBuilder} is null
+     */
+    protected final B getBuilder() {
+        if(securityBuilder == null) {
+            throw new IllegalStateException("securityBuilder cannot be null");
+        }
+        return securityBuilder;
+    }
+
+    /**
+     * Performs post processing of an object. The default is to delegate to the
+     * {@link ObjectPostProcessor}.
+     *
+     * @param object the Object to post process
+     * @return the possibly modified Object to use
+     */
+    @SuppressWarnings("unchecked")
+    protected <T> T postProcess(T object) {
+        return (T) this.objectPostProcessor.postProcess(object);
+    }
+
+    /**
+     * Adds an {@link ObjectPostProcessor} to be used for this
+     * {@link SecurityConfigurerAdapter}. The default implementation does
+     * nothing to the object.
+     *
+     * @param objectPostProcessor the {@link ObjectPostProcessor} to use
+     */
+    public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
+        this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
+    }
+
+    /**
+     * Sets the {@link SecurityBuilder} to be used. This is automatically set
+     * when using
+     * {@link AbstractConfiguredSecurityBuilder#apply(SecurityConfigurerAdapter)}
+     *
+     * @param builder the {@link SecurityBuilder} to set
+     */
+    public void setBuilder(B builder) {
+        this.securityBuilder = builder;
+    }
+
+    /**
+     * An {@link ObjectPostProcessor} that delegates work to numerous
+     * {@link ObjectPostProcessor} implementations.
+     *
+     * @author Rob Winch
+     */
+    private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
+        private List<ObjectPostProcessor<? extends Object>> postProcessors = new ArrayList<ObjectPostProcessor<?>>();
+
+        @Override
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public Object postProcess(Object object) {
+            for(ObjectPostProcessor opp : postProcessors) {
+                Class<?> oppClass = opp.getClass();
+                Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass,ObjectPostProcessor.class);
+                if(oppType == null || oppType.isAssignableFrom(object.getClass())) {
+                    object = opp.postProcess(object);
+                }
+            }
+            return object;
+        }
+
+        /**
+         * Adds an {@link ObjectPostProcessor} to use
+         * @param objectPostProcessor the {@link ObjectPostProcessor} to add
+         * @return true if the {@link ObjectPostProcessor} was added, else false
+         */
+        private boolean addObjectPostProcessor(ObjectPostProcessor<?extends Object> objectPostProcessor) {
+            return this.postProcessors.add(objectPostProcessor);
+        }
+    }
+}

+ 44 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/ProviderManagerBuilder.java

@@ -0,0 +1,44 @@
+/*
+ * 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.authentication;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.config.annotation.SecurityBuilder;
+
+/**
+ * Interface for operating on a SecurityBuilder that creates a {@link ProviderManager}
+ *
+ * @author Rob Winch
+ *
+ * @param <B> the type of the {@link SecurityBuilder}
+ */
+public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> extends SecurityBuilder<AuthenticationManager> {
+
+    /**
+     * Add authentication based upon the custom {@link AuthenticationProvider}
+     * that is passed in. Since the {@link AuthenticationProvider}
+     * implementation is unknown, all customizations must be done externally and
+     * the {@link ProviderManagerBuilder} is returned immediately.
+     *
+     * @return a {@link ProviderManagerBuilder} to allow further authentication
+     *         to be provided to the {@link ProviderManagerBuilder}
+     * @throws Exception
+     *             if an error occurs when adding the {@link AuthenticationProvider}
+     */
+    B authenticationProvider(AuthenticationProvider authenticationProvider);
+}

+ 254 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java

@@ -0,0 +1,254 @@
+/*
+ * 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.authentication.builders;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.security.authentication.AuthenticationEventPublisher;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsAwareConfigurer;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.util.Assert;
+
+/**
+ * {@link SecurityBuilder} used to create an {@link AuthenticationManager}.
+ * Allows for easily building in memory authentication, LDAP authentication,
+ * JDBC based authentication, adding {@link UserDetailsService}, and adding
+ * {@link AuthenticationProvider}'s.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder> implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
+
+    private AuthenticationManager parentAuthenticationManager;
+    private List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>();
+    private UserDetailsService defaultUserDetailsService;
+    private Boolean eraseCredentials;
+    private AuthenticationEventPublisher eventPublisher;
+
+    /**
+     * Creates a new instance
+     */
+    public AuthenticationManagerBuilder() {
+        super(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR,true);
+    }
+
+    /**
+     * Allows providing a parent {@link AuthenticationManager} that will be
+     * tried if this {@link AuthenticationManager} was unable to attempt to
+     * authenticate the provided {@link Authentication}.
+     *
+     * @param authenticationManager
+     *            the {@link AuthenticationManager} that should be used if the
+     *            current {@link AuthenticationManager} was unable to attempt to
+     *            authenticate the provided {@link Authentication}.
+     * @return the {@link AuthenticationManagerBuilder} for further adding types
+     *         of authentication
+     */
+    public AuthenticationManagerBuilder parentAuthenticationManager(
+            AuthenticationManager authenticationManager) {
+        this.parentAuthenticationManager = authenticationManager;
+        return this;
+    }
+
+    /**
+     * Sets the {@link AuthenticationEventPublisher}
+     *
+     * @param eventPublisher
+     *            the {@link AuthenticationEventPublisher} to use
+     * @return the {@link AuthenticationManagerBuilder} for further
+     *         customizations
+     */
+    public AuthenticationManagerBuilder authenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
+        Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
+        this.eventPublisher = eventPublisher;
+        return this;
+    }
+
+    /**
+     *
+     *
+     * @param eraseCredentials
+     *            true if {@link AuthenticationManager} should clear the
+     *            credentials from the {@link Authentication} object after
+     *            authenticating
+     * @return the {@link AuthenticationManagerBuilder} for further customizations
+     */
+    public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
+        this.eraseCredentials = eraseCredentials;
+        return this;
+    }
+
+
+    /**
+     * Add in memory authentication to the {@link AuthenticationManagerBuilder}
+     * and return a {@link InMemoryUserDetailsManagerConfigurer} to
+     * allow customization of the in memory authentication.
+     *
+     * <p>
+     * This method also ensure that a {@link UserDetailsService} is available
+     * for the {@link #getDefaultUserDetailsService()} method. Note that
+     * additional {@link UserDetailsService}'s may override this
+     * {@link UserDetailsService} as the default.
+     * </p>
+     *
+     * @return a {@link InMemoryUserDetailsManagerConfigurer} to allow
+     *         customization of the in memory authentication
+     * @throws Exception
+     *             if an error occurs when adding the in memory authentication
+     */
+    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
+            throws Exception {
+        return apply(new InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
+    }
+
+    /**
+     * Add JDBC authentication to the {@link AuthenticationManagerBuilder} and
+     * return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
+     * JDBC authentication.
+     *
+     * <p>
+     * This method also ensure that a {@link UserDetailsService} is available
+     * for the {@link #getDefaultUserDetailsService()} method. Note that
+     * additional {@link UserDetailsService}'s may override this
+     * {@link UserDetailsService} as the default.
+     * </p>
+     *
+     * @return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
+     * JDBC authentication
+     * @throws Exception if an error occurs when adding the JDBC authentication
+     */
+    public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
+            throws Exception {
+        return apply(new JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
+    }
+
+    /**
+     * Add authentication based upon the custom {@link UserDetailsService} that
+     * is passed in. It then returns a {@link DaoAuthenticationConfigurer} to
+     * allow customization of the authentication.
+     *
+     * <p>
+     * This method also ensure that the {@link UserDetailsService} is available
+     * for the {@link #getDefaultUserDetailsService()} method. Note that
+     * additional {@link UserDetailsService}'s may override this
+     * {@link UserDetailsService} as the default.
+     * </p>
+     *
+     * @return a {@link DaoAuthenticationConfigurer} to allow customization
+     *         of the DAO authentication
+     * @throws Exception
+     *             if an error occurs when adding the {@link UserDetailsService}
+     *             based authentication
+     */
+    public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T> userDetailsService(
+            T userDetailsService) throws Exception {
+        this.defaultUserDetailsService = userDetailsService;
+        return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T>(userDetailsService));
+    }
+
+    /**
+     * Add LDAP authentication to the {@link AuthenticationManagerBuilder} and
+     * return a {@link LdapAuthenticationProviderConfigurer} to allow
+     * customization of the LDAP authentication.
+     *
+     * <p>
+     * This method <b>does NOT</b> ensure that a {@link UserDetailsService} is
+     * available for the {@link #getDefaultUserDetailsService()} method.
+     * </p>
+     *
+     * @return a {@link LdapAuthenticationProviderConfigurer} to allow
+     *         customization of the LDAP authentication
+     * @throws Exception
+     *             if an error occurs when adding the LDAP authentication
+     */
+    public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication()
+            throws Exception {
+        return apply(new LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder>());
+    }
+
+    /**
+     * Add authentication based upon the custom {@link AuthenticationProvider}
+     * that is passed in. Since the {@link AuthenticationProvider}
+     * implementation is unknown, all customizations must be done externally and
+     * the {@link AuthenticationManagerBuilder} is returned immediately.
+     *
+     * <p>
+     * This method <b>does NOT</b> ensure that the {@link UserDetailsService} is
+     * available for the {@link #getDefaultUserDetailsService()} method.
+     * </p>
+     *
+     * @return a {@link AuthenticationManagerBuilder} to allow further authentication
+     *         to be provided to the {@link AuthenticationManagerBuilder}
+     * @throws Exception
+     *             if an error occurs when adding the {@link AuthenticationProvider}
+     */
+    public AuthenticationManagerBuilder authenticationProvider(
+            AuthenticationProvider authenticationProvider) {
+        this.authenticationProviders.add(authenticationProvider);
+        return this;
+    }
+
+    @Override
+    protected ProviderManager performBuild() throws Exception {
+        ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager);
+        if(eraseCredentials != null) {
+            providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
+        }
+        if(eventPublisher != null) {
+            providerManager.setAuthenticationEventPublisher(eventPublisher);
+        }
+        providerManager = postProcess(providerManager);
+        return providerManager;
+    }
+
+    /**
+     * Gets the default {@link UserDetailsService} for the
+     * {@link AuthenticationManagerBuilder}. The result may be null in some
+     * circumstances.
+     *
+     * @return the default {@link UserDetailsService} for the
+     * {@link AuthenticationManagerBuilder}
+     */
+    public UserDetailsService getDefaultUserDetailsService() {
+        return this.defaultUserDetailsService;
+    }
+
+    /**
+     * Captures the {@link UserDetailsService} from any {@link UserDetailsAwareConfigurer}.
+     *
+     * @param configurer the {@link UserDetailsAwareConfigurer} to capture the {@link UserDetailsService} from.
+     * @return the {@link UserDetailsAwareConfigurer} for further customizations
+     * @throws Exception if an error occurs
+     */
+    private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder,? extends UserDetailsService>> C apply(C configurer) throws Exception {
+        this.defaultUserDetailsService = configurer.getUserDetailsService();
+        return (C) super.apply(configurer);
+    }
+}

+ 469 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java

@@ -0,0 +1,469 @@
+/*
+ * 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.authentication.configurers.ldap;
+
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.encoding.PasswordEncoder;
+import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
+import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
+import org.springframework.security.ldap.authentication.AbstractLdapAuthenticator;
+import org.springframework.security.ldap.authentication.BindAuthenticator;
+import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
+import org.springframework.security.ldap.authentication.LdapAuthenticator;
+import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
+import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
+import org.springframework.security.ldap.search.LdapUserSearch;
+import org.springframework.security.ldap.server.ApacheDSContainer;
+import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
+import org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper;
+import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
+import org.springframework.security.ldap.userdetails.PersonContextMapper;
+import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
+
+/**
+ * Configures LDAP {@link AuthenticationProvider} in the {@link ProviderManagerBuilder}.
+ *
+ * @param <B> the {@link ProviderManagerBuilder} type that this is configuring.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>> extends SecurityConfigurerAdapter<AuthenticationManager,B> {
+    private String groupRoleAttribute = "cn";
+    private String groupSearchBase = "";
+    private String groupSearchFilter = "(uniqueMember={0})";
+    private String rolePrefix = "ROLE_";
+    private String userSearchBase = ""; // only for search
+    private String userSearchFilter = null;//"uid={0}"; // only for search
+    private String[] userDnPatterns;
+    private BaseLdapPathContextSource contextSource;
+    private ContextSourceBuilder contextSourceBuilder = new ContextSourceBuilder();
+    private UserDetailsContextMapper userDetailsContextMapper;
+    private PasswordEncoder passwordEncoder;
+    private String passwordAttribute;
+
+    private LdapAuthenticationProvider build() throws Exception {
+        BaseLdapPathContextSource contextSource = getContextSource();
+        LdapAuthenticator ldapAuthenticator = createLdapAuthenticator(contextSource);
+
+        DefaultLdapAuthoritiesPopulator authoritiesPopulator = new DefaultLdapAuthoritiesPopulator(
+                contextSource, groupSearchBase);
+        authoritiesPopulator.setGroupRoleAttribute(groupRoleAttribute);
+        authoritiesPopulator.setGroupSearchFilter(groupSearchFilter);
+
+        LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(
+                ldapAuthenticator, authoritiesPopulator);
+        SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
+        simpleAuthorityMapper.setPrefix(rolePrefix);
+        simpleAuthorityMapper.afterPropertiesSet();
+        ldapAuthenticationProvider.setAuthoritiesMapper(simpleAuthorityMapper);
+        if(userDetailsContextMapper != null) {
+            ldapAuthenticationProvider.setUserDetailsContextMapper(userDetailsContextMapper);
+        }
+        return ldapAuthenticationProvider;
+    }
+
+    /**
+     * Creates the {@link LdapAuthenticator} to use
+     *
+     * @param contextSource the {@link BaseLdapPathContextSource} to use
+     * @return the {@link LdapAuthenticator} to use
+     */
+    private LdapAuthenticator createLdapAuthenticator(BaseLdapPathContextSource contextSource) {
+        AbstractLdapAuthenticator ldapAuthenticator = passwordEncoder == null ? createBindAuthenticator(contextSource) : createPasswordCompareAuthenticator(contextSource);
+        LdapUserSearch userSearch = createUserSearch();
+        if(userSearch != null) {
+            ldapAuthenticator.setUserSearch(userSearch);
+        }
+        if(userDnPatterns != null && userDnPatterns.length > 0) {
+            ldapAuthenticator.setUserDnPatterns(userDnPatterns);
+        }
+        return postProcess(ldapAuthenticator);
+    }
+
+    /**
+     * Creates {@link PasswordComparisonAuthenticator}
+     *
+     * @param contextSource the {@link BaseLdapPathContextSource} to use
+     * @return
+     */
+    private PasswordComparisonAuthenticator createPasswordCompareAuthenticator(BaseLdapPathContextSource contextSource) {
+        PasswordComparisonAuthenticator ldapAuthenticator = new PasswordComparisonAuthenticator(contextSource);
+        ldapAuthenticator.setPasswordAttributeName(passwordAttribute);
+        ldapAuthenticator.setPasswordEncoder(passwordEncoder);
+        return ldapAuthenticator;
+    }
+
+    /**
+     * Creates a {@link BindAuthenticator}
+     *
+     * @param contextSource the {@link BaseLdapPathContextSource} to use
+     * @return the {@link BindAuthenticator} to use
+     */
+    private BindAuthenticator createBindAuthenticator(
+            BaseLdapPathContextSource contextSource) {
+        return new BindAuthenticator(contextSource);
+    }
+
+    private LdapUserSearch createUserSearch() {
+        if(userSearchFilter == null) {
+            return null;
+        }
+        return new FilterBasedLdapUserSearch(userSearchBase, userSearchFilter, contextSource);
+    }
+
+    /**
+     * Specifies the {@link BaseLdapPathContextSource} to be used. If not
+     * specified, an embedded LDAP server will be created using
+     * {@link #contextSource()}.
+     *
+     * @param contextSource
+     *            the {@link BaseLdapPathContextSource} to use
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further
+     *         customizations
+     * @see #contextSource()
+     */
+    public LdapAuthenticationProviderConfigurer<B> contextSource(BaseLdapPathContextSource contextSource) {
+        this.contextSource = contextSource;
+        return this;
+    }
+
+    /**
+     * Allows easily configuring of a {@link BaseLdapPathContextSource} with
+     * defaults pointing to an embedded LDAP server that is created.
+     *
+     * @return the {@link ContextSourceBuilder} for further customizations
+     */
+    public ContextSourceBuilder contextSource() {
+        return contextSourceBuilder;
+    }
+
+    /**
+     * Specifies the {@link PasswordEncoder} to be used when authenticating with
+     * password comparison.
+     *
+     * @param passwordEncoder the {@link PasswordEncoder} to use
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further customization
+     */
+    public LdapAuthenticationProviderConfigurer<B> passwordEncoder(PasswordEncoder passwordEncoder) {
+        this.passwordEncoder = passwordEncoder;
+        return this;
+    }
+
+    /**
+     * If your users are at a fixed location in the directory (i.e. you can work
+     * out the DN directly from the username without doing a directory search),
+     * you can use this attribute to map directly to the DN. It maps directly to
+     * the userDnPatterns property of AbstractLdapAuthenticator. The value is a
+     * specific pattern used to build the user's DN, for example
+     * "uid={0},ou=people". The key "{0}" must be present and will be
+     * substituted with the username.
+     *
+     * @param userDnPatterns the LDAP patterns for finding the usernames
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
+     */
+    public LdapAuthenticationProviderConfigurer<B> userDnPatterns(String...userDnPatterns) {
+        this.userDnPatterns = userDnPatterns;
+        return this;
+    }
+
+    /**
+     * Allows explicit customization of the loaded user object by specifying a
+     * UserDetailsContextMapper bean which will be called with the context
+     * information from the user's directory entry.
+     *
+     * @param userDetailsContextMapper the {@link UserDetailsContextMapper} to use
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further
+     *         customizations
+     *
+     * @see PersonContextMapper
+     * @see InetOrgPersonContextMapper
+     * @see LdapUserDetailsMapper
+     */
+    public LdapAuthenticationProviderConfigurer<B> userDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
+        this.userDetailsContextMapper = userDetailsContextMapper;
+        return this;
+    }
+
+    /**
+     * Specifies the attribute name which contains the role name. Default is "cn".
+     * @param groupRoleAttribute the attribute name that maps a group to a role.
+     * @return
+     */
+    public LdapAuthenticationProviderConfigurer<B> groupRoleAttribute(String groupRoleAttribute) {
+        this.groupRoleAttribute = groupRoleAttribute;
+        return this;
+    }
+
+    /**
+     * The search base for group membership searches. Defaults to "".
+     * @param groupSearchBase
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
+     */
+    public LdapAuthenticationProviderConfigurer<B> groupSearchBase(String groupSearchBase) {
+        this.groupSearchBase = groupSearchBase;
+        return this;
+    }
+
+    /**
+     * The LDAP filter to search for groups. Defaults to "(uniqueMember={0})".
+     * The substituted parameter is the DN of the user.
+     *
+     * @param groupSearchFilter the LDAP filter to search for groups
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
+     */
+    public LdapAuthenticationProviderConfigurer<B> groupSearchFilter(String groupSearchFilter) {
+        this.groupSearchFilter = groupSearchFilter;
+        return this;
+    }
+
+    /**
+     * A non-empty string prefix that will be added as a prefix to the existing
+     * roles. The default is "ROLE_".
+     *
+     * @param rolePrefix the prefix to be added to the roles that are loaded.
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
+     * @see SimpleAuthorityMapper#setPrefix(String)
+     */
+    public LdapAuthenticationProviderConfigurer<B> rolePrefix(String rolePrefix) {
+        this.rolePrefix = rolePrefix;
+        return this;
+    }
+    /**
+     * Search base for user searches. Defaults to "". Only used with {@link #userSearchFilter(String)}.
+     *
+     * @param userSearchBase search base for user searches
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
+     */
+    public LdapAuthenticationProviderConfigurer<B> userSearchBase(String userSearchBase) {
+        this.userSearchBase = userSearchBase;
+        return this;
+    }
+
+    /**
+     * The LDAP filter used to search for users (optional). For example
+     * "(uid={0})". The substituted parameter is the user's login name.
+     *
+     * @param userSearchFilter
+     *            the LDAP filter used to search for users
+     * @return the {@link LdapAuthenticationProviderConfigurer} for further
+     *         customizations
+     */
+    public LdapAuthenticationProviderConfigurer<B> userSearchFilter(String userSearchFilter) {
+        this.userSearchFilter = userSearchFilter;
+        return this;
+    }
+
+    @Override
+    public void configure(B builder) throws Exception {
+        LdapAuthenticationProvider provider = postProcess(build());
+        builder.authenticationProvider(provider);
+    }
+
+    /**
+     * Sets up Password based comparison
+     *
+     * @author Rob Winch
+     */
+    public final class PasswordCompareConfigurer {
+
+        /**
+         * Allows specifying the {@link PasswordEncoder} to use. The default is {@link PlaintextPasswordEncoder}.
+         * @param passwordEncoder the {@link PasswordEncoder} to use
+         * @return the {@link PasswordEncoder} to use
+         */
+        public PasswordCompareConfigurer passwordEncoder(PasswordEncoder passwordEncoder) {
+            LdapAuthenticationProviderConfigurer.this.passwordEncoder = passwordEncoder;
+            return this;
+        }
+
+        /**
+         * The attribute in the directory which contains the user password. Defaults to "userPassword".
+         *
+         * @param passwordAttribute the attribute in the directory which contains the user password
+         * @return the {@link PasswordCompareConfigurer} for further customizations
+         */
+        public PasswordCompareConfigurer passwordAttribute(String passwordAttribute) {
+            LdapAuthenticationProviderConfigurer.this.passwordAttribute = passwordAttribute;
+            return this;
+        }
+
+        /**
+         * Allows obtaining a reference to the
+         * {@link LdapAuthenticationProviderConfigurer} for further
+         * customizations
+         *
+         * @return attribute in the directory which contains the user password
+         */
+        public LdapAuthenticationProviderConfigurer<B> and() {
+            return LdapAuthenticationProviderConfigurer.this;
+        }
+
+        private PasswordCompareConfigurer() {}
+    }
+
+    /**
+     * Allows building a {@link BaseLdapPathContextSource} and optionally
+     * creating an embedded LDAP instance.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    public final class ContextSourceBuilder {
+        private String ldif = "classpath*:*.ldif";
+        private String managerPassword;
+        private String managerDn;
+        private int port = 33389;
+        private String root = "dc=springframework,dc=org";
+        private String url;
+
+        /**
+         * Specifies an ldif to load at startup for an embedded LDAP server.
+         * This only loads if using an embedded instance. The default is
+         * "classpath*:*.ldif".
+         *
+         * @param ldif
+         *            the ldif to load at startup for an embedded LDAP server.
+         * @return the {@link ContextSourceBuilder} for further customization
+         */
+        public ContextSourceBuilder ldif(String ldif) {
+            this.ldif = ldif;
+            return this;
+        }
+
+        /**
+         * Username (DN) of the "manager" user identity (i.e.
+         * "uid=admin,ou=system") which will be used to authenticate to a
+         * (non-embedded) LDAP server. If omitted, anonymous access will be
+         * used.
+         *
+         * @param managerDn
+         *            the username (DN) of the "manager" user identity used to
+         *            authenticate to a LDAP server.
+         * @return the {@link ContextSourceBuilder} for further customization
+         */
+        public ContextSourceBuilder managerDn(String managerDn) {
+            this.managerDn = managerDn;
+            return this;
+        }
+
+        /**
+         * The password for the manager DN. This is required if the manager-dn is specified.
+         * @param managerPassword password for the manager DN
+         * @return the {@link ContextSourceBuilder} for further customization
+         */
+        public ContextSourceBuilder managerPassword(String managerPassword) {
+            this.managerPassword  = managerPassword;
+            return this;
+        }
+
+        /**
+         * The port to connect to LDAP to (the default is 33389).
+         * @param port the port to connect to
+         * @return the {@link ContextSourceBuilder} for further customization
+         */
+        public ContextSourceBuilder port(int port) {
+            this.port = port;
+            return this;
+        }
+
+        /**
+         * Optional root suffix for the embedded LDAP server. Default is
+         * "dc=springframework,dc=org"
+         *
+         * @param root
+         *            root suffix for the embedded LDAP server
+         * @return the {@link ContextSourceBuilder} for further customization
+         */
+        public ContextSourceBuilder root(String root) {
+            this.root  = root;
+            return this;
+        }
+
+        /**
+         * Specifies the ldap server URL when not using the embedded LDAP
+         * server. For example, "ldaps://ldap.example.com:33389/dc=myco,dc=org".
+         *
+         * @param url
+         *            the ldap server URL
+         * @return the {@link ContextSourceBuilder} for further customization
+         */
+        public ContextSourceBuilder url(String url) {
+            this.url  = url;
+            return this;
+        }
+
+        /**
+         * Gets the {@link LdapAuthenticationProviderConfigurer} for further
+         * customizations
+         *
+         * @return the {@link LdapAuthenticationProviderConfigurer} for further
+         *         customizations
+         */
+        public LdapAuthenticationProviderConfigurer<B> and() {
+            return LdapAuthenticationProviderConfigurer.this;
+        }
+
+        private DefaultSpringSecurityContextSource build() throws Exception {
+            DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(getProviderUrl());
+            if(managerDn != null) {
+                contextSource.setUserDn(managerDn);
+                if(managerPassword == null) {
+                    throw new IllegalStateException("managerPassword is required if managerDn is supplied");
+                }
+                contextSource.setPassword(managerPassword);
+            }
+            contextSource = postProcess(contextSource);
+            if(url != null) {
+                return contextSource;
+            }
+            ApacheDSContainer apacheDsContainer = new ApacheDSContainer(root, ldif);
+            apacheDsContainer.setPort(port);
+            postProcess(apacheDsContainer);
+            return contextSource;
+        }
+
+        private String getProviderUrl() {
+            if(url == null) {
+                return "ldap://127.0.0.1:" + port + "/" + root;
+            }
+            return url;
+        }
+
+        private ContextSourceBuilder() {}
+    }
+
+    private BaseLdapPathContextSource getContextSource() throws Exception {
+        if(contextSource == null) {
+            contextSource = contextSourceBuilder.build();
+        }
+        return contextSource;
+    }
+
+    /**
+     * @return
+     */
+    public PasswordCompareConfigurer passwordCompare() {
+        return new PasswordCompareConfigurer()
+            .passwordAttribute("password")
+            .passwordEncoder(new PlaintextPasswordEncoder());
+    }
+}

+ 42 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/InMemoryUserDetailsManagerConfigurer.java

@@ -0,0 +1,42 @@
+/*
+ * 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.authentication.configurers.provisioning;
+
+import java.util.ArrayList;
+
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+
+/**
+ * Configures an {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} to
+ * have in memory authentication. It also allows easily adding users to the in memory authentication.
+ *
+ * @param <B> the type of the {@link SecurityBuilder} that is being configured
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>> extends
+        UserDetailsManagerConfigurer<B,InMemoryUserDetailsManagerConfigurer<B>> {
+
+    /**
+     * Creates a new instance
+     */
+    public InMemoryUserDetailsManagerConfigurer() {
+        super(new InMemoryUserDetailsManager(new ArrayList<UserDetails>()));
+    }
+}

+ 192 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/JdbcUserDetailsManagerConfigurer.java

@@ -0,0 +1,192 @@
+/*
+ * 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.authentication.configurers.provisioning;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.jdbc.datasource.init.DataSourceInitializer;
+import org.springframework.jdbc.datasource.init.DatabasePopulator;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.core.userdetails.UserCache;
+import org.springframework.security.provisioning.JdbcUserDetailsManager;
+
+/**
+ * Configures an {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} to
+ * have JDBC authentication. It also allows easily adding users to the database used for authentication and setting up
+ * the schema.
+ *
+ * <p>
+ * The only required method is the {@link #dataSource(javax.sql.DataSource)} all other methods have reasonable defaults.
+ * </p>
+ *
+ * @param <B> the type of the {@link ProviderManagerBuilder} that is being configured
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class JdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>> extends
+        UserDetailsManagerConfigurer<B,JdbcUserDetailsManagerConfigurer<B>> {
+
+    private DataSource dataSource;
+
+    private List<Resource> initScripts = new ArrayList<Resource>();
+
+    public JdbcUserDetailsManagerConfigurer(JdbcUserDetailsManager manager) {
+        super(manager);
+    }
+
+    public JdbcUserDetailsManagerConfigurer() {
+        this(new JdbcUserDetailsManager());
+    }
+
+
+    /**
+     * Populates the {@link DataSource} to be used. This is the only required attribute.
+     *
+     * @param dataSource the {@link DataSource} to be used. Cannot be null.
+     * @return
+     * @throws Exception
+     */
+    public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) throws Exception {
+        this.dataSource = dataSource;
+        getUserDetailsService().setDataSource(dataSource);
+        return this;
+    }
+
+    /**
+    * Sets the query to be used for finding a user by their username. For example:
+    *
+    * <code>
+    *     select username,password,enabled from users where username = ?
+    * </code>
+    * @param query  The query to use for selecting the username, password, and if the user is enabled by username.
+    *               Must contain a single parameter for the username.
+    * @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
+    * @throws Exception
+    */
+    public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query) throws Exception {
+        getUserDetailsService().setUsersByUsernameQuery(query);
+        return this;
+    }
+
+    /**
+     * Sets the query to be used for finding a user's authorities by their username. For example:
+     *
+     * <code>
+     *     select username,authority from authorities where username = ?
+     * </code>
+     *
+     * @param query  The query to use for selecting the username, authority  by username.
+     *               Must contain a single parameter for the username.
+     * @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
+     * @throws Exception
+     */
+    public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query) throws Exception {
+        getUserDetailsService().setAuthoritiesByUsernameQuery(query);
+        return this;
+    }
+
+    /**
+     * An SQL statement to query user's group authorities given a username. For example:
+     *
+     * <code>
+     *     select
+     *         g.id, g.group_name, ga.authority
+     *     from
+     *         groups g, group_members gm, group_authorities ga
+     *     where
+     *         gm.username = ? and g.id = ga.group_id and g.id = gm.group_id
+     * </code>
+     *
+     * @param query  The query to use for selecting the authorities by group.
+     *               Must contain a single parameter for the username.
+     * @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
+     * @throws Exception
+     */
+    public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query) throws Exception {
+        JdbcUserDetailsManager userDetailsService = getUserDetailsService();
+        userDetailsService.setEnableGroups(true);
+        userDetailsService.setGroupAuthoritiesByUsernameQuery(query);
+        return this;
+    }
+
+    /**
+     * A non-empty string prefix that will be added to role strings loaded from persistent storage (default is "").
+     *
+     * @param rolePrefix
+     * @return
+     * @throws Exception
+     */
+    public JdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix) throws Exception {
+        getUserDetailsService().setRolePrefix(rolePrefix);
+        return this;
+    }
+
+
+    /**
+     * Defines the {@link UserCache} to use
+     *
+     * @param userCache the {@link UserCache} to use
+     * @return the {@link JdbcUserDetailsManagerConfigurer} for further customizations
+     * @throws Exception
+     */
+    public JdbcUserDetailsManagerConfigurer<B> userCache(UserCache userCache) throws Exception {
+        getUserDetailsService().setUserCache(userCache);
+        return this;
+    }
+
+    @Override
+    protected void initUserDetailsService() throws Exception {
+        if(!initScripts.isEmpty()) {
+            getDataSourceInit().afterPropertiesSet();
+        }
+        super.initUserDetailsService();
+    }
+
+    @Override
+    public JdbcUserDetailsManager getUserDetailsService() {
+        return (JdbcUserDetailsManager) super.getUserDetailsService();
+    }
+
+    /**
+     * Populates the default schema that allows users and authorities to be stored.
+     *
+     * @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
+     */
+    public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema() {
+        this.initScripts.add(new ClassPathResource("org/springframework/security/core/userdetails/jdbc/users.ddl"));
+        return this;
+    }
+
+    protected DatabasePopulator getDatabasePopulator() {
+        ResourceDatabasePopulator dbp = new ResourceDatabasePopulator();
+        dbp.setScripts(initScripts.toArray(new Resource[initScripts.size()]));
+        return dbp;
+    }
+
+    private DataSourceInitializer getDataSourceInit() {
+        DataSourceInitializer dsi = new DataSourceInitializer();
+        dsi.setDatabasePopulator(getDatabasePopulator());
+        dsi.setDataSource(dataSource);
+        return dsi;
+    }
+}

+ 263 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/UserDetailsManagerConfigurer.java

@@ -0,0 +1,263 @@
+/*
+ * 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.authentication.configurers.provisioning;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.UserDetailsManager;
+import org.springframework.util.Assert;
+
+/**
+ * Base class for populating an
+ * {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} with a
+ * {@link UserDetailsManager}.
+ *
+ * @param <B> the type of the {@link SecurityBuilder} that is being configured
+ * @param <C> the type of {@link UserDetailsManagerConfigurer}
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsManagerConfigurer<B,C>> extends
+        UserDetailsServiceConfigurer<B,C,UserDetailsManager> {
+
+    private final List<UserDetailsBuilder> userBuilders = new ArrayList<UserDetailsBuilder>();
+
+    protected UserDetailsManagerConfigurer(UserDetailsManager userDetailsManager) {
+        super(userDetailsManager);
+    }
+
+    /**
+     * Populates the users that have been added.
+     *
+     * @throws Exception
+     */
+    @Override
+    protected void initUserDetailsService() throws Exception {
+        for(UserDetailsBuilder userBuilder : userBuilders) {
+            getUserDetailsService().createUser(userBuilder.build());
+        }
+    }
+
+    /**
+     * Allows adding a user to the {@link UserDetailsManager} that is being created. This method can be invoked
+     * multiple times to add multiple users.
+     *
+     * @param username the username for the user being added. Cannot be null.
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public final UserDetailsBuilder withUser(String username) {
+        UserDetailsBuilder userBuilder = new UserDetailsBuilder((C)this);
+        userBuilder.username(username);
+        this.userBuilders.add(userBuilder);
+        return userBuilder;
+    }
+
+    /**
+     * Builds the user to be added. At minimum the username, password, and authorities should provided. The remaining
+     * attributes have reasonable defaults.
+     *
+     * @param <T> the type of {@link UserDetailsManagerConfigurer} to return for chaining methods.
+     */
+    public class UserDetailsBuilder {
+        private String username;
+        private String password;
+        private List<GrantedAuthority> authorities;
+        private boolean accountExpired;
+        private boolean accountLocked;
+        private boolean credentialsExpired;
+        private boolean disabled;
+        private final C builder;
+
+        /**
+         * Creates a new instance
+         * @param builder the builder to return
+         */
+        private UserDetailsBuilder(C builder) {
+            this.builder = builder;
+        }
+
+        /**
+         * Returns the {@link UserDetailsManagerRegistry} for method chaining (i.e. to add another user)
+         *
+         * @return the {@link UserDetailsManagerRegistry} for method chaining (i.e. to add another user)
+         */
+        public C and() {
+            return builder;
+        }
+
+        /**
+         * Populates the username. This attribute is required.
+         *
+         * @param username the username. Cannot be null.
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         */
+        private UserDetailsBuilder username(String username) {
+            Assert.notNull(username, "username cannot be null");
+            this.username = username;
+            return this;
+        }
+
+        /**
+         * Populates the password. This attribute is required.
+         *
+         * @param password the password. Cannot be null.
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         */
+        public UserDetailsBuilder password(String password) {
+            Assert.notNull(password, "password cannot be null");
+            this.password = password;
+            return this;
+        }
+
+        /**
+         * Populates the roles. This method is a shortcut for calling {@link #authorities(String...)}, but automatically
+         * prefixes each entry with "ROLE_". This means the following:
+         *
+         * <code>
+         *     builder.roles("USER","ADMIN");
+         * </code>
+         *
+         * is equivalent to
+         *
+         * <code>
+         *     builder.authorities("ROLE_USER","ROLE_ADMIN");
+         * </code>
+         *
+         * <p>This attribute is required, but can also be populated with {@link #authorities(String...)}.</p>
+         *
+         * @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null, contain null values or start
+         *              with "ROLE_"
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         */
+        public UserDetailsBuilder roles(String... roles) {
+            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length);
+            for(String role : roles) {
+                Assert.isTrue(!role.startsWith("ROLE_"), role + " cannot start with ROLE_ (it is automatically added)");
+                authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
+            }
+            return authorities(authorities);
+        }
+
+        /**
+         * Populates the authorities. This attribute is required.
+         *
+         * @param authorities the authorities for this user. Cannot be null, or contain null
+         *                    values
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         * @see #roles(String...)
+         */
+        public UserDetailsBuilder authorities(GrantedAuthority...authorities) {
+            return authorities(Arrays.asList(authorities));
+        }
+
+        /**
+         * Populates the authorities. This attribute is required.
+         *
+         * @param authorities the authorities for this user. Cannot be null, or contain null
+         *                    values
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         * @see #roles(String...)
+         */
+        public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) {
+            this.authorities = new ArrayList<GrantedAuthority>(authorities);
+            return this;
+        }
+
+        /**
+         * Populates the authorities. This attribute is required.
+         *
+         * @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN, etc). Cannot be null, or contain null
+         *                    values
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         * @see #roles(String...)
+         */
+        public UserDetailsBuilder authorities(String... authorities) {
+            return authorities(AuthorityUtils.createAuthorityList(authorities));
+        }
+
+        /**
+         * Defines if the account is expired or not. Default is false.
+         *
+         * @param accountExpired true if the account is expired, false otherwise
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         */
+        public UserDetailsBuilder accountExpired(boolean accountExpired) {
+            this.accountExpired = accountExpired;
+            return this;
+        }
+
+        /**
+         * Defines if the account is locked or not. Default is false.
+         *
+         * @param accountLocked true if the account is locked, false otherwise
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         */
+        public UserDetailsBuilder accountLocked(boolean accountLocked) {
+            this.accountLocked = accountLocked;
+            return this;
+        }
+
+        /**
+         * Defines if the credentials are expired or not. Default is false.
+         *
+         * @param credentialsExpired true if the credentials are expired, false otherwise
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         */
+        public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) {
+            this.credentialsExpired = credentialsExpired;
+            return this;
+        }
+
+
+        /**
+         * Defines if the account is disabled or not. Default is false.
+         *
+         * @param disabled true if the account is disabled, false otherwise
+         * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
+         *         user)
+         */
+        public UserDetailsBuilder disabled(boolean disabled) {
+            this.disabled = disabled;
+            return this;
+        }
+
+        private UserDetails build() {
+            return new User(username, password, !disabled, !accountExpired,
+                    !credentialsExpired, !accountLocked, authorities);
+        }
+    }
+}

+ 95 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/AbstractDaoAuthenticationConfigurer.java

@@ -0,0 +1,95 @@
+/*
+ * 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.authentication.configurers.userdetails;
+
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * Allows configuring a {@link DaoAuthenticationProvider}
+ *
+ * @author Rob Winch
+ * @since 3.2
+ *
+ * @param <B> the type of the {@link SecurityBuilder}
+ * @param <C> the type of {@link AbstractDaoAuthenticationConfigurer} this is
+ * @param <U> The type of {@link UserDetailsService} that is being used
+ *
+ */
+abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, C extends AbstractDaoAuthenticationConfigurer<B,C,U>,U extends UserDetailsService> extends UserDetailsAwareConfigurer<B,U> {
+    private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+    private final U userDetailsService;
+
+    /**
+     * Creates a new instance
+     *
+     * @param userDetailsService
+     */
+    protected AbstractDaoAuthenticationConfigurer(U userDetailsService) {
+        this.userDetailsService = userDetailsService;
+        provider.setUserDetailsService(userDetailsService);
+    }
+
+    /**
+     * Allows specifying the {@link PasswordEncoder} to use with the {@link DaoAuthenticationProvider}. The default is
+     * is to use plain text.
+     *
+     * @param passwordEncoder The {@link PasswordEncoder} to use.
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public C passwordEncoder(PasswordEncoder passwordEncoder) {
+        provider.setPasswordEncoder(passwordEncoder);
+        return (C) this;
+    }
+
+    /**
+     * Allows specifying the
+     * {@link org.springframework.security.authentication.encoding.PasswordEncoder}
+     * to use with the {@link DaoAuthenticationProvider}. The default is is to
+     * use plain text.
+     *
+     * @param passwordEncoder
+     *            The
+     *            {@link org.springframework.security.authentication.encoding.PasswordEncoder}
+     *            to use.
+     * @return the {@link SecurityConfigurer} for further customizations
+     */
+    @SuppressWarnings("unchecked")
+    public C passwordEncoder(org.springframework.security.authentication.encoding.PasswordEncoder passwordEncoder) {
+        provider.setPasswordEncoder(passwordEncoder);
+        return (C) this;
+    }
+
+    @Override
+    public void configure(B builder) throws Exception {
+        provider = postProcess(provider);
+        builder.authenticationProvider(provider);
+    }
+
+    /**
+     * Gets the {@link UserDetailsService} that is used with the {@link DaoAuthenticationProvider}
+     *
+     * @return the {@link UserDetailsService} that is used with the {@link DaoAuthenticationProvider}
+     */
+    public U getUserDetailsService() {
+        return userDetailsService;
+    }
+}

+ 41 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/DaoAuthenticationConfigurer.java

@@ -0,0 +1,41 @@
+/*
+ * 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.authentication.configurers.userdetails;
+
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+/**
+* Allows configuring a {@link DaoAuthenticationProvider}
+*
+* @author Rob Winch
+* @since 3.2
+*
+* @param <B> The type of {@link ProviderManagerBuilder} this is
+* @param <U> The type of {@link UserDetailsService} that is being used
+*
+*/
+public class DaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService> extends AbstractDaoAuthenticationConfigurer<B,DaoAuthenticationConfigurer<B,U>, U>{
+
+    /**
+     * Creates a new instance
+     * @param userDetailsService
+     */
+    public DaoAuthenticationConfigurer(U userDetailsService) {
+        super(userDetailsService);
+    }
+}

+ 39 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/UserDetailsAwareConfigurer.java

@@ -0,0 +1,39 @@
+/*
+ * 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.authentication.configurers.userdetails;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+/**
+ * Base class that allows access to the {@link UserDetailsService} for using as a default value with {@link AuthenticationManagerBuilder}.
+ *
+ * @author Rob Winch
+ *
+ * @param <B> the type of the {@link ProviderManagerBuilder}
+ * @param <U> the type of {@link UserDetailsService}
+ */
+public abstract class UserDetailsAwareConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService> extends SecurityConfigurerAdapter<AuthenticationManager,B> {
+
+    /**
+     * Gets the {@link UserDetailsService} or null if it is not available
+     * @return the {@link UserDetailsService} or null if it is not available
+     */
+    public abstract U getUserDetailsService();
+}

+ 58 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/userdetails/UserDetailsServiceConfigurer.java

@@ -0,0 +1,58 @@
+/*
+ * 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.authentication.configurers.userdetails;
+
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+/**
+ * Allows configuring a {@link UserDetailsService} within a {@link AuthenticationManagerBuilder}.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ *
+ * @param <B> the type of the {@link SecurityBuilder}
+ * @param <C> the {@link SecurityConfigurer} (or this)
+ * @param <U> the type of UserDetailsService being used to allow for returning the concrete UserDetailsService.
+ */
+public class UserDetailsServiceConfigurer<B extends ProviderManagerBuilder<B>,
+        C extends UserDetailsServiceConfigurer<B, C, U>,
+        U extends UserDetailsService>
+            extends AbstractDaoAuthenticationConfigurer<B, C, U> {
+
+    /**
+     * Creates a new instance
+     * @param userDetailsService the {@link UserDetailsService} that should be used
+     */
+    public UserDetailsServiceConfigurer(U userDetailsService) {
+        super(userDetailsService);
+    }
+
+    @Override
+    public void configure(B builder) throws Exception {
+        initUserDetailsService();
+
+        super.configure(builder);
+    }
+
+    /**
+     * Allows subclasses to initialize the {@link UserDetailsService}. For example, it might add users, initialize
+     * schema, etc.
+     */
+    protected void initUserDetailsService() throws Exception {}
+}

+ 77 - 0
config/src/main/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java

@@ -0,0 +1,77 @@
+/*
+ * 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.configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.Aware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.util.Assert;
+
+/**
+ * Allows registering Objects to participate with an
+ * {@link AutowireCapableBeanFactory}'s post processing of {@link Aware}
+ * methods, {@link InitializingBean#afterPropertiesSet()}, and
+ * {@link DisposableBean#destroy()}.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+final class AutowireBeanFactoryObjectPostProcessor implements ObjectPostProcessor<Object>, DisposableBean {
+    private final Log logger = LogFactory.getLog(getClass());
+    private final AutowireCapableBeanFactory autowireBeanFactory;
+    private final List<DisposableBean> disposableBeans = new ArrayList<DisposableBean>();
+
+    public AutowireBeanFactoryObjectPostProcessor(
+            AutowireCapableBeanFactory autowireBeanFactory) {
+        Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null");
+        this.autowireBeanFactory = autowireBeanFactory;
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.Initializer#initialize(java.lang.Object)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T postProcess(T object) {
+        T result = (T) autowireBeanFactory.initializeBean(object, null);
+        if(result instanceof DisposableBean) {
+            disposableBeans.add((DisposableBean) result);
+        }
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.beans.factory.DisposableBean#destroy()
+     */
+    @Override
+    public void destroy() throws Exception {
+        for(DisposableBean disposable : disposableBeans) {
+            try {
+                disposable.destroy();
+            } catch(Exception error) {
+                logger.error(error);
+            }
+        }
+    }
+
+}

+ 44 - 0
config/src/main/java/org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.java

@@ -0,0 +1,44 @@
+/*
+ * 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.configuration;
+
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+
+/**
+ * Spring {@link Configuration} that exports the default
+ * {@link ObjectPostProcessor}. This class is not intended to be imported
+ * manually rather it is imported automatically when using
+ * {@link EnableWebSecurity} or {@link EnableGlobalMethodSecurity}.
+ *
+ * @see EnableWebSecurity
+ * @see EnableGlobalMethodSecurity
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+@Configuration
+public class ObjectPostProcessorConfiguration {
+
+    @Bean
+    public ObjectPostProcessor<Object> objectPostProcessor(AutowireCapableBeanFactory beanFactory) {
+        return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
+    }
+}

+ 101 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.java

@@ -0,0 +1,101 @@
+/*
+ * 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.method.configuration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.AdviceMode;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.Ordered;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
+
+/**
+ * <p>Enables Spring Security global method security similar to the
+ * <global-method-security> xml support.</p>
+ *
+ * <p>
+ * More advanced configurations may wish to extend
+ * {@link GlobalMethodSecurityConfiguration} and override the protected methods
+ * to provide custom implementations. Note that
+ * {@link EnableGlobalMethodSecurity} still must be included on the class
+ * extending {@link GlobalMethodSecurityConfiguration} to determine the
+ * settings.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(value={java.lang.annotation.ElementType.TYPE})
+@Documented
+@Import({GlobalMethodSecuritySelector.class,ObjectPostProcessorConfiguration.class})
+public @interface EnableGlobalMethodSecurity {
+
+    /**
+     * Determines if Spring Security's pre post annotations should be enabled. Default is false.
+     * @return true if pre post annotations should be enabled false otherwise.
+     */
+    boolean prePostEnabled() default false;
+
+    /**
+     * Determines if Spring Security's {@link Secured} annotations should be enabled.
+     * @return true if {@link Secured} annotations should be enabled false otherwise. Default is false.
+     */
+    boolean securedEnabled() default false;
+
+    /**
+     * Determines if JSR-250 annotations should be enabled. Default is false.
+     * @return true if JSR-250 should be enabled false otherwise.
+     */
+    boolean jsr250Enabled() default false;
+
+    /**
+     * Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as
+     * opposed to standard Java interface-based proxies ({@code false}). The default is
+     * {@code false}. <strong>Applicable only if {@link #mode()} is set to
+     * {@link AdviceMode#PROXY}</strong>.
+     *
+     * <p>Note that setting this attribute to {@code true} will affect <em>all</em>
+     * Spring-managed beans requiring proxying, not just those marked with
+     * the Security annotations. For example, other beans marked with Spring's
+     * {@code @Transactional} annotation will be upgraded to subclass proxying at the same
+     * time. This approach has no negative impact in practice unless one is explicitly
+     * expecting one type of proxy vs another, e.g. in tests.
+     *
+     * @return true if CGILIB proxies should be created instead of interface based proxies, else false
+     */
+    boolean proxyTargetClass() default false;
+
+    /**
+     * Indicate how security advice should be applied. The default is
+     * {@link AdviceMode#PROXY}.
+     * @see AdviceMode
+     *
+     * @return the {@link AdviceMode} to use
+     */
+    AdviceMode mode() default AdviceMode.PROXY;
+
+    /**
+     * Indicate the ordering of the execution of the security advisor
+     * when multiple advices are applied at a specific joinpoint.
+     * The default is {@link Ordered#LOWEST_PRECEDENCE}.
+     *
+     * @return the order the security advisor should be applied
+     */
+    int order() default Ordered.LOWEST_PRECEDENCE;
+}

+ 68 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityAspectJAutoProxyRegistrar.java

@@ -0,0 +1,68 @@
+/*
+ * 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.method.configuration;
+
+import java.util.Map;
+
+import org.springframework.aop.config.AopConfigUtils;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+
+/**
+ * Registers an
+ * {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
+ * AnnotationAwareAspectJAutoProxyCreator} against the current
+ * {@link BeanDefinitionRegistry} as appropriate based on a given @
+ * {@link EnableGlobalMethodSecurity} annotation.
+ *
+ * <p>
+ * Note: This class is necessary because AspectJAutoProxyRegistrar only supports
+ * EnableAspectJAutoProxy.
+ * </p>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+class GlobalMethodSecurityAspectJAutoProxyRegistrar implements
+        ImportBeanDefinitionRegistrar {
+
+    /**
+     * Register, escalate, and configure the AspectJ auto proxy creator based on
+     * the value of the @{@link EnableGlobalMethodSecurity#proxyTargetClass()}
+     * attribute on the importing {@code @Configuration} class.
+     */
+    @Override
+    public void registerBeanDefinitions(
+            AnnotationMetadata importingClassMetadata,
+            BeanDefinitionRegistry registry) {
+
+        AopConfigUtils
+                .registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
+
+        Map<String, Object> annotationAttributes = importingClassMetadata
+                .getAnnotationAttributes(EnableGlobalMethodSecurity.class
+                        .getName());
+        AnnotationAttributes enableAJAutoProxy = AnnotationAttributes
+                .fromMap(annotationAttributes);
+
+        if (enableAJAutoProxy.getBoolean("proxyTargetClass")) {
+            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
+        }
+    }
+
+}

+ 385 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java

@@ -0,0 +1,385 @@
+/*
+ * 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.method.configuration;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.springframework.aop.framework.ProxyFactoryBean;
+import org.springframework.aop.target.LazyInitTargetSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportAware;
+import org.springframework.context.annotation.Role;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.AfterInvocationProvider;
+import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
+import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory;
+import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice;
+import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.intercept.AfterInvocationManager;
+import org.springframework.security.access.intercept.AfterInvocationProviderManager;
+import org.springframework.security.access.intercept.RunAsManager;
+import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
+import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor;
+import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
+import org.springframework.security.access.method.MethodSecurityMetadataSource;
+import org.springframework.security.access.prepost.PostInvocationAdviceProvider;
+import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice;
+import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter;
+import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
+import org.springframework.security.access.vote.AffirmativeBased;
+import org.springframework.security.access.vote.AuthenticatedVoter;
+import org.springframework.security.access.vote.RoleVoter;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.util.Assert;
+
+/**
+ * Base {@link Configuration} for enabling global method security. Classes may
+ * extend this class to customize the defaults, but must be sure to specify the
+ * {@link EnableGlobalMethodSecurity} annotation on the subclass.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see EnableGlobalMethodSecurity
+ */
+@Configuration
+public class GlobalMethodSecurityConfiguration implements ImportAware {
+    @Autowired
+    private ApplicationContext context;
+    @Autowired(required=false)
+    private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
+        @Override
+        public <T> T postProcess(T object) {
+            throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @"+EnableGlobalMethodSecurity.class.getName());
+        }
+    };
+    private AuthenticationManager authenticationManager;
+    private AuthenticationManagerBuilder auth = new AuthenticationManagerBuilder();
+    private boolean disableAuthenticationRegistry;
+    private AnnotationAttributes enableMethodSecurity;
+    private MethodSecurityExpressionHandler expressionHandler;
+
+    /**
+     * Creates the default MethodInterceptor which is a MethodSecurityInterceptor using the following methods to
+     * construct it.
+     * <ul>
+     *     <li>{@link #accessDecisionManager()}</li>
+     *     <li>{@link #afterInvocationManager()}</li>
+     *     <li>{@link #authenticationManager()}</li>
+     *     <li>{@link #methodSecurityMetadataSource()}</li>
+     *     <li>{@link #runAsManager()}</li>
+     *
+     * </ul>
+     *
+     * <p>
+     *     Subclasses can override this method to provide a different {@link MethodInterceptor}.
+     * </p>
+     *
+     * @return
+     * @throws Exception
+     */
+    @Bean
+    public MethodInterceptor methodSecurityInterceptor() throws Exception {
+        MethodSecurityInterceptor methodSecurityInterceptor = new MethodSecurityInterceptor();
+        methodSecurityInterceptor
+                .setAccessDecisionManager(accessDecisionManager());
+        methodSecurityInterceptor
+                .setAfterInvocationManager(afterInvocationManager());
+        methodSecurityInterceptor
+                .setAuthenticationManager(authenticationManager());
+        methodSecurityInterceptor
+                .setSecurityMetadataSource(methodSecurityMetadataSource());
+        RunAsManager runAsManager = runAsManager();
+        if (runAsManager != null) {
+            methodSecurityInterceptor.setRunAsManager(runAsManager);
+        }
+        return methodSecurityInterceptor;
+    }
+
+    /**
+     * Provide a custom {@link AfterInvocationManager} for the default
+     * implementation of {@link #methodSecurityInterceptor()}. The default is
+     * null if pre post is not enabled. Otherwise, it returns a {@link AfterInvocationProviderManager}.
+     *
+     * <p>
+     * Subclasses should override this method to provide a custom {@link AfterInvocationManager}
+     * </p>
+     *
+     * @return
+     */
+    protected AfterInvocationManager afterInvocationManager() {
+        if(prePostEnabled()) {
+            AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager();
+            ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(getExpressionHandler());
+            PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider(postAdvice);
+            List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<AfterInvocationProvider>();
+            afterInvocationProviders.add(postInvocationAdviceProvider);
+            invocationProviderManager.setProviders(afterInvocationProviders);
+            return invocationProviderManager;
+        }
+        return null;
+    }
+
+    /**
+     * Provide a custom {@link RunAsManager} for the default implementation of
+     * {@link #methodSecurityInterceptor()}. The default is null.
+     *
+     * @return
+     */
+    protected RunAsManager runAsManager() {
+        return null;
+    }
+
+    /**
+     * Allows subclasses to provide a custom {@link AccessDecisionManager}. The default is a {@link AffirmativeBased}
+     * with the following voters:
+     *
+     * <ul>
+     *     <li>{@link PreInvocationAuthorizationAdviceVoter}</li>
+     *     <li>{@link RoleVoter} </li>
+     *     <li>{@link AuthenticatedVoter} </li>
+     * </ul>
+     *
+     * @return
+     */
+    @SuppressWarnings("rawtypes")
+    protected AccessDecisionManager accessDecisionManager() {
+        List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
+        ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
+        expressionAdvice.setExpressionHandler(getExpressionHandler());
+
+        decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(
+                expressionAdvice));
+        decisionVoters.add(new RoleVoter());
+        decisionVoters.add(new AuthenticatedVoter());
+        return new AffirmativeBased(decisionVoters);
+    }
+
+    /**
+     * Provide a {@link MethodSecurityExpressionHandler} that is
+     * registered with the {@link ExpressionBasedPreInvocationAdvice}. The default is
+     * {@link DefaultMethodSecurityExpressionHandler}
+     *
+     * <p>Subclasses may override this method to provide a custom {@link MethodSecurityExpressionHandler}</p>
+     *
+     * @return
+     */
+    protected MethodSecurityExpressionHandler expressionHandler() {
+        return new DefaultMethodSecurityExpressionHandler();
+    }
+
+    /**
+     * Gets the {@link MethodSecurityExpressionHandler} or creates it using {@link #expressionHandler}.
+     *
+     * @return a non {@code null} {@link MethodSecurityExpressionHandler}
+     */
+    protected final MethodSecurityExpressionHandler getExpressionHandler() {
+        if(expressionHandler == null) {
+            expressionHandler = expressionHandler();
+        }
+        return expressionHandler;
+    }
+
+    /**
+     * Provides a custom {@link MethodSecurityMetadataSource} that is registered
+     * with the {@link #methodSecurityMetadataSource()}. Default is null.
+     *
+     * @return a custom {@link MethodSecurityMetadataSource} that is registered
+     * with the {@link #methodSecurityMetadataSource()}
+     */
+    protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
+        return null;
+    }
+
+    /**
+     * Allows providing a custom {@link AuthenticationManager}. The default is
+     * to use any authentication mechanisms registered by {@link #registerAuthentication(AuthenticationManagerBuilder)}. If
+     * {@link #registerAuthentication(AuthenticationManagerBuilder)} was not overriden, then an {@link AuthenticationManager}
+     * is attempted to be autowired by type.
+     *
+     * @return
+     */
+    protected AuthenticationManager authenticationManager() throws Exception {
+        if(authenticationManager == null) {
+            DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
+            auth.authenticationEventPublisher(eventPublisher);
+            auth.objectPostProcessor(objectPostProcessor);
+            registerAuthentication(auth);
+            if(!disableAuthenticationRegistry) {
+                authenticationManager = auth.build();
+            }
+            if(authenticationManager == null) {
+                authenticationManager = lazyBean(AuthenticationManager.class);
+            }
+        }
+        return authenticationManager;
+    }
+
+    /**
+     * Sub classes can override this method to register different types of authentication. If not overridden,
+     * {@link #registerAuthentication(AuthenticationManagerBuilder)} will attempt to autowire by type.
+     *
+     * @param auth the {@link AuthenticationManagerBuilder} used to register different authentication mechanisms for the
+     *                 global method security.
+     * @throws Exception
+     */
+    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+        this.disableAuthenticationRegistry = true;
+    }
+
+    /**
+     * Provides the default {@link MethodSecurityMetadataSource} that will be
+     * used. It creates a {@link DelegatingMethodSecurityMetadataSource} based
+     * upon {@link #customMethodSecurityMetadataSource()} and the attributes on
+     * {@link EnableGlobalMethodSecurity}.
+     *
+     * @return
+     */
+    @Bean
+    public MethodSecurityMetadataSource methodSecurityMetadataSource() {
+        List<MethodSecurityMetadataSource> sources = new ArrayList<MethodSecurityMetadataSource>();
+        ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
+                methodExpressionHandler());
+        MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
+        if (customMethodSecurityMetadataSource != null) {
+            sources.add(customMethodSecurityMetadataSource);
+        }
+        if (prePostEnabled()) {
+            sources.add(new PrePostAnnotationSecurityMetadataSource(
+                    attributeFactory));
+        }
+        if (securedEnabled()) {
+            sources.add(new SecuredAnnotationSecurityMetadataSource());
+        }
+        if (jsr250Enabled()) {
+            sources.add(new Jsr250MethodSecurityMetadataSource());
+        }
+        return new DelegatingMethodSecurityMetadataSource(sources);
+    }
+
+    /**
+     * Creates the {@link MethodSecurityExpressionHandler} to be used.
+     *
+     * @return
+     */
+    @Bean
+    public MethodSecurityExpressionHandler methodExpressionHandler() {
+        return new DefaultMethodSecurityExpressionHandler();
+    }
+
+    /**
+     * Creates the {@link PreInvocationAuthorizationAdvice} to be used. The
+     * default is {@link ExpressionBasedPreInvocationAdvice}.
+     *
+     * @return
+     */
+    @Bean
+    public PreInvocationAuthorizationAdvice preInvocationAuthorizationAdvice() {
+        ExpressionBasedPreInvocationAdvice preInvocationAdvice = new ExpressionBasedPreInvocationAdvice();
+        preInvocationAdvice.setExpressionHandler(methodExpressionHandler());
+        return preInvocationAdvice;
+    }
+
+    /**
+     * Obtains the {@link MethodSecurityMetadataSourceAdvisor} to be used.
+     *
+     * @return
+     */
+    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+    @Bean
+    public MethodSecurityMetadataSourceAdvisor metaDataSourceAdvisor() {
+        MethodSecurityMetadataSourceAdvisor methodAdvisor = new MethodSecurityMetadataSourceAdvisor(
+                "methodSecurityInterceptor", methodSecurityMetadataSource(),
+                "methodSecurityMetadataSource");
+        methodAdvisor.setOrder(order());
+        return methodAdvisor;
+    }
+
+    /**
+     * Obtains the attributes from {@link EnableGlobalMethodSecurity} if this class was imported using the {@link EnableGlobalMethodSecurity} annotation.
+     */
+    @Override
+    public final void setImportMetadata(AnnotationMetadata importMetadata) {
+        Map<String, Object> annotationAttributes = importMetadata
+                .getAnnotationAttributes(EnableGlobalMethodSecurity.class
+                        .getName());
+        enableMethodSecurity = AnnotationAttributes
+                .fromMap(annotationAttributes);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T lazyBean(Class<T> interfaceName) {
+        LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
+        String[] beanNamesForType = context.getBeanNamesForType(interfaceName);
+        Assert.isTrue(beanNamesForType.length == 1 , "Expecting to only find a single bean for type " + interfaceName + ", but found " + Arrays.asList(beanNamesForType));
+        lazyTargetSource.setTargetBeanName(beanNamesForType[0]);
+        lazyTargetSource.setBeanFactory(context);
+        ProxyFactoryBean proxyFactory = new ProxyFactoryBean();
+        proxyFactory.setTargetSource(lazyTargetSource);
+        proxyFactory.setInterfaces(new Class[] { interfaceName });
+        return (T) proxyFactory.getObject();
+    }
+
+    private boolean prePostEnabled() {
+        return enableMethodSecurity().getBoolean("prePostEnabled");
+    }
+
+    private boolean securedEnabled() {
+        return enableMethodSecurity().getBoolean("securedEnabled");
+    }
+
+    private boolean jsr250Enabled() {
+        return enableMethodSecurity().getBoolean("jsr250Enabled");
+    }
+
+    private int order() {
+        return (Integer) enableMethodSecurity().get("order");
+    }
+
+    private AnnotationAttributes enableMethodSecurity() {
+        if (enableMethodSecurity == null) {
+            // if it is null look at this instance (i.e. a subclass was used)
+            EnableGlobalMethodSecurity methodSecurityAnnotation = AnnotationUtils
+                    .findAnnotation(getClass(),
+                            EnableGlobalMethodSecurity.class);
+            Assert.notNull(methodSecurityAnnotation,
+                    EnableGlobalMethodSecurity.class.getName() + " is required");
+            Map<String, Object> methodSecurityAttrs = AnnotationUtils
+                    .getAnnotationAttributes(methodSecurityAnnotation);
+            this.enableMethodSecurity = AnnotationAttributes
+                    .fromMap(methodSecurityAttrs);
+        }
+        return this.enableMethodSecurity;
+    }
+
+}

+ 59 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecuritySelector.java

@@ -0,0 +1,59 @@
+/*
+ * 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.method.configuration;
+
+import java.util.Map;
+
+import org.springframework.context.annotation.AdviceMode;
+import org.springframework.context.annotation.AutoProxyRegistrar;
+import org.springframework.context.annotation.ImportSelector;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Dynamically determines which imports to include using the
+ * {@link EnableGlobalMethodSecurity} annotation.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+final class GlobalMethodSecuritySelector implements ImportSelector {
+
+    @Override
+    public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
+        Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
+        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(annoType.getName(), false);
+        AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationAttributes);
+        Assert.notNull(attributes, String.format(
+                "@%s is not present on importing class '%s' as expected",
+                annoType.getSimpleName(), importingClassMetadata.getClassName()));
+
+        // TODO would be nice if could use BeanClassLoaderAware (does not work)
+        Class<?> importingClass = ClassUtils.resolveClassName(importingClassMetadata.getClassName(), ClassUtils.getDefaultClassLoader());
+        boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class.isAssignableFrom(importingClass);
+
+        AdviceMode mode = attributes.getEnum("mode");
+        String autoProxyClassName = AdviceMode.PROXY == mode ? AutoProxyRegistrar.class.getName()
+                : GlobalMethodSecurityAspectJAutoProxyRegistrar.class.getName();
+        if(skipMethodSecurityConfiguration) {
+            return new String[] { autoProxyClassName };
+        }
+        return new String[] { autoProxyClassName,
+                GlobalMethodSecurityConfiguration.class.getName()};
+    }
+}

+ 198 - 0
config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherConfigurer.java

@@ -0,0 +1,198 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer;
+import org.springframework.security.web.util.AntPathRequestMatcher;
+import org.springframework.security.web.util.AnyRequestMatcher;
+import org.springframework.security.web.util.RegexRequestMatcher;
+import org.springframework.security.web.util.RequestMatcher;
+
+/**
+ * A base class for registering {@link RequestMatcher}'s. For example, it might allow for specifying which
+ * {@link RequestMatcher} require a certain level of authorization.
+ *
+ *
+ * @param <B> The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer}
+ * @param <C> The object that is returned or Chained after creating the RequestMatcher
+ * @param <O> The Object being built by Builder B
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public abstract class AbstractRequestMatcherConfigurer<B extends SecurityBuilder<O>,C,O> extends SecurityConfigurerAdapter<O,B> {
+    private static final RequestMatcher ANY_REQUEST = new AnyRequestMatcher();
+    /**
+     * Maps any request.
+     *
+     * @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
+     * @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
+     *                    from
+     *
+     * @return the object that is chained after creating the {@link RequestMatcher}
+     */
+    public C anyRequest() {
+        return requestMatchers(ANY_REQUEST);
+    }
+
+    /**
+     * Maps a {@link List} of {@link org.springframework.security.web.util.AntPathRequestMatcher} instances.
+     *
+     * @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
+     * @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
+     *                    from
+     *
+     * @return the object that is chained after creating the {@link RequestMatcher}
+     */
+    public C antMatchers(HttpMethod method, String... antPatterns) {
+        return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
+    }
+
+    /**
+     * Maps a {@link List} of {@link org.springframework.security.web.util.AntPathRequestMatcher} instances that do
+     * not care which {@link HttpMethod} is used.
+     *
+     * @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
+     *                    from
+     *
+     * @return the object that is chained after creating the {@link RequestMatcher}
+     */
+    public C antMatchers(String... antPatterns) {
+        return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
+    }
+
+    /**
+     * Maps a {@link List} of {@link org.springframework.security.web.util.RegexRequestMatcher} instances.
+     *
+     * @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
+     * @param regexPatterns the regular expressions to create
+     *                      {@link org.springframework.security.web.util.RegexRequestMatcher} from
+     *
+     * @return the object that is chained after creating the {@link RequestMatcher}
+     */
+    public C regexMatchers(HttpMethod method, String... regexPatterns) {
+        return chainRequestMatchers(RequestMatchers.regexMatchers(method,
+                regexPatterns));
+    }
+
+    /**
+     * Create a {@link List} of {@link org.springframework.security.web.util.RegexRequestMatcher} instances that do not
+     * specify an {@link HttpMethod}.
+     *
+     * @param regexPatterns the regular expressions to create
+     *                      {@link org.springframework.security.web.util.RegexRequestMatcher} from
+     *
+     * @return the object that is chained after creating the {@link RequestMatcher}
+     */
+    public C regexMatchers(String... regexPatterns) {
+        return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
+    }
+
+    /**
+     * Associates a list of {@link RequestMatcher} instances with the {@link AbstractRequestMatcherMappingConfigurer}
+     *
+     * @param requestMatchers the {@link RequestMatcher} instances
+     *
+     * @return the object that is chained after creating the {@link RequestMatcher}
+     */
+    public C requestMatchers(RequestMatcher... requestMatchers) {
+        return chainRequestMatchers(Arrays.asList(requestMatchers));
+    }
+
+    /**
+     * Subclasses should implement this method for returning the object that is chained to the creation of the
+     * {@link RequestMatcher} instances.
+     *
+     * @param requestMatchers the {@link RequestMatcher} instances that were created
+     * @return the chained Object for the subclass which allows association of something else to the
+     *         {@link RequestMatcher}
+     */
+    protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);
+
+    /**
+     * Utilities for creating {@link RequestMatcher} instances.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    private static final class RequestMatchers {
+
+        /**
+         * Create a {@link List} of {@link AntPathRequestMatcher} instances.
+         *
+         * @param httpMethod the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
+         * @param antPatterns the ant patterns to create {@link AntPathRequestMatcher} from
+         *
+         * @return a {@link List} of {@link AntPathRequestMatcher} instances
+         */
+        public static List<RequestMatcher> antMatchers(HttpMethod httpMethod, String...antPatterns) {
+            String method = httpMethod == null ? null : httpMethod.toString();
+            List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
+            for(String pattern : antPatterns) {
+                matchers.add(new AntPathRequestMatcher(pattern, method));
+            }
+            return matchers;
+        }
+
+        /**
+         * Create a {@link List} of {@link AntPathRequestMatcher} instances that do not specify an {@link HttpMethod}.
+         *
+         * @param antPatterns the ant patterns to create {@link AntPathRequestMatcher} from
+         *
+         * @return a {@link List} of {@link AntPathRequestMatcher} instances
+         */
+        public static List<RequestMatcher> antMatchers(String...antPatterns) {
+            return antMatchers(null, antPatterns);
+        }
+
+        /**
+         * Create a {@link List} of {@link RegexRequestMatcher} instances.
+         *
+         * @param httpMethod the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
+         * @param regexPatterns the regular expressions to create {@link RegexRequestMatcher} from
+         *
+         * @return a {@link List} of {@link RegexRequestMatcher} instances
+         */
+        public static List<RequestMatcher> regexMatchers(HttpMethod httpMethod, String...regexPatterns) {
+            String method = httpMethod == null ? null : httpMethod.toString();
+            List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
+            for(String pattern : regexPatterns) {
+                matchers.add(new RegexRequestMatcher(pattern, method));
+            }
+            return matchers;
+        }
+
+        /**
+         * Create a {@link List} of {@link RegexRequestMatcher} instances that do not specify an {@link HttpMethod}.
+         *
+         *  @param regexPatterns the regular expressions to create {@link RegexRequestMatcher} from
+         *
+         * @return a {@link List} of {@link RegexRequestMatcher} instances
+         */
+        public static List<RequestMatcher> regexMatchers(String...regexPatterns) {
+            return regexMatchers(null, regexPatterns);
+        }
+
+        private RequestMatchers() {}
+    }
+}

+ 177 - 0
config/src/main/java/org/springframework/security/config/annotation/web/HttpSecurityBuilder.java

@@ -0,0 +1,177 @@
+/*
+ * 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;
+
+import javax.servlet.Filter;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.openid.OpenIDAuthenticationFilter;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.security.web.access.channel.ChannelProcessingFilter;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
+import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
+import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
+import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
+import org.springframework.security.web.context.SecurityContextPersistenceFilter;
+import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
+import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
+import org.springframework.security.web.session.ConcurrentSessionFilter;
+import org.springframework.security.web.session.SessionManagementFilter;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ * @param <H>
+ */
+public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>> extends SecurityBuilder<DefaultSecurityFilterChain> {
+
+    /**
+     * Gets the {@link SecurityConfigurer} by its class name or
+     * <code>null</code> if not found. Note that object hierarchies are not
+     * considered.
+     *
+     * @param clazz the Class of the {@link SecurityConfigurer} to attempt to get.
+     */
+    <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(
+            Class<C> clazz);
+
+    /**
+     * Removes the {@link SecurityConfigurer} by its class name or
+     * <code>null</code> if not found. Note that object hierarchies are not
+     * considered.
+     *
+     * @param clazz the Class of the {@link SecurityConfigurer} to attempt to remove.
+     * @return the {@link SecurityConfigurer} that was removed or null if not found
+     */
+    <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
+
+    /**
+     * Sets an object that is shared by multiple {@link SecurityConfigurer}.
+     *
+     * @param sharedType the Class to key the shared object by.
+     * @param object the Object to store
+     */
+    <C> void setSharedObject(Class<C> sharedType, C object);
+
+    /**
+     * Gets a shared Object. Note that object heirarchies are not considered.
+     *
+     * @param sharedType the type of the shared Object
+     * @return the shared Object or null if it is not found
+     */
+    <C> C getSharedObject(Class<C> sharedType);
+
+    /**
+     * Allows adding an additional {@link AuthenticationProvider} to be used
+     *
+     * @param authenticationProvider the {@link AuthenticationProvider} to be added
+     * @return the {@link HttpSecurity} for further customizations
+     */
+    H authenticationProvider(
+            AuthenticationProvider authenticationProvider);
+
+    /**
+     * Allows adding an additional {@link UserDetailsService} to be used
+     *
+     * @param userDetailsService the {@link UserDetailsService} to be added
+     * @return the {@link HttpSecurity} for further customizations
+     */
+    H userDetailsService(
+            UserDetailsService userDetailsService) throws Exception;
+
+    /**
+     * Allows adding a {@link Filter} after one of the known {@link Filter}
+     * classes. The known {@link Filter} instances are either a {@link Filter}
+     * listed in {@link #addFilter(Filter)} or a {@link Filter} that has already
+     * been added using {@link #addFilterAfter(Filter, Class)} or
+     * {@link #addFilterBefore(Filter, Class)}.
+     *
+     * @param filter the {@link Filter} to register before the type {@code afterFilter}
+     * @param afterFilter the Class of the known {@link Filter}.
+     * @return the {@link HttpSecurity} for further customizations
+     */
+    H addFilterAfter(Filter filter,
+            Class<? extends Filter> afterFilter);
+
+    /**
+     * Allows adding a {@link Filter} before one of the known {@link Filter}
+     * classes. The known {@link Filter} instances are either a {@link Filter}
+     * listed in {@link #addFilter(Filter)} or a {@link Filter} that has already
+     * been added using {@link #addFilterAfter(Filter, Class)} or
+     * {@link #addFilterBefore(Filter, Class)}.
+     *
+     * @param filter the {@link Filter} to register before the type {@code beforeFilter}
+     * @param beforeFilter the Class of the known {@link Filter}.
+     * @return the {@link HttpSecurity} for further customizations
+     */
+    H addFilterBefore(Filter filter,
+            Class<? extends Filter> beforeFilter);
+
+    /**
+     * Adds a {@link Filter} that must be an instance of or extend one of the
+     * Filters provided within the Security framework. The method ensures that
+     * the ordering of the Filters is automatically taken care of.
+     *
+     * The ordering of the Filters is:
+     *
+     * <ul>
+     * <li>{@link ChannelProcessingFilter}</li>
+     * <li>{@link ConcurrentSessionFilter}</li>
+     * <li>{@link SecurityContextPersistenceFilter}</li>
+     * <li>{@link LogoutFilter}</li>
+     * <li>{@link X509AuthenticationFilter}</li>
+     * <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
+     * <li>{@link org.springframework.security.cas.web.CasAuthenticationFilter}</li>
+     * <li>{@link UsernamePasswordAuthenticationFilter}</li>
+     * <li>{@link ConcurrentSessionFilter}</li>
+     * <li>{@link OpenIDAuthenticationFilter}</li>
+     * <li>{@link DefaultLoginPageViewFilter}</li>
+     * <li>{@link ConcurrentSessionFilter}</li>
+     * <li>{@link DigestAuthenticationFilter}</li>
+     * <li>{@link BasicAuthenticationFilter}</li>
+     * <li>{@link RequestCacheAwareFilter}</li>
+     * <li>{@link SecurityContextHolderAwareRequestFilter}</li>
+     * <li>{@link JaasApiIntegrationFilter}</li>
+     * <li>{@link RememberMeAuthenticationFilter}</li>
+     * <li>{@link AnonymousAuthenticationFilter}</li>
+     * <li>{@link SessionManagementFilter}</li>
+     * <li>{@link ExceptionTranslationFilter}</li>
+     * <li>{@link FilterSecurityInterceptor}</li>
+     * <li>{@link SwitchUserFilter}</li>
+     * </ul>
+     *
+     * @param filter the {@link Filter} to add
+     * @return the {@link HttpSecurity} for further customizations
+     */
+    H addFilter(Filter filter);
+
+    // FIXME shared object or explicit?
+    AuthenticationManager getAuthenticationManager();
+}

+ 41 - 0
config/src/main/java/org/springframework/security/config/annotation/web/WebSecurityConfigurer.java

@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import javax.servlet.Filter;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+/**
+ * Allows customization to the {@link WebSecurity}. In most instances
+ * users will use {@link EnableWebSecurity} and a create {@link Configuration}
+ * that extends {@link WebSecurityConfigurerAdapter} which will automatically be
+ * applied to the {@link WebSecurity} by the {@link EnableWebSecurity}
+ * annotation.
+ *
+ * @see WebSecurityConfigurerAdapter
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> {
+
+}

+ 191 - 0
config/src/main/java/org/springframework/security/config/annotation/web/builders/DebugFilter.java

@@ -0,0 +1,191 @@
+/*
+ * 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.builders;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.util.UrlUtils;
+
+/**
+ * Spring Security debugging filter.
+ * <p>
+ * Logs information (such as session creation) to help the user understand how requests are being handled
+ * by Spring Security and provide them with other relevant information (such as when sessions are being created).
+ *
+ *
+ * @author Luke Taylor
+ * @author Rob Winch
+ * @since 3.1
+ */
+class DebugFilter implements Filter {
+    private static final String ALREADY_FILTERED_ATTR_NAME = DebugFilter.class.getName().concat(".FILTERED");
+
+    private final FilterChainProxy fcp;
+    private final Logger logger = new Logger();
+
+    public DebugFilter(FilterChainProxy fcp) {
+        this.fcp = fcp;
+    }
+
+    public final void doFilter(ServletRequest srvltRequest, ServletResponse srvltResponse, FilterChain filterChain)
+            throws ServletException, IOException {
+
+        if (!(srvltRequest instanceof HttpServletRequest) || !(srvltResponse instanceof HttpServletResponse)) {
+            throw new ServletException("DebugFilter just supports HTTP requests");
+        }
+        HttpServletRequest request = (HttpServletRequest) srvltRequest;
+        HttpServletResponse response = (HttpServletResponse) srvltResponse;
+
+        List<Filter> filters = getFilters(request);
+        logger.log("Request received for '" + UrlUtils.buildRequestUrl(request) + "':\n\n" +
+                request + "\n\n" +
+                "servletPath:" + request.getServletPath() + "\n" +
+                "pathInfo:" + request.getPathInfo() + "\n\n" +
+                formatFilters(filters));
+
+        if (request.getAttribute(ALREADY_FILTERED_ATTR_NAME) == null) {
+            invokeWithWrappedRequest(request, response, filterChain);
+        } else {
+            fcp.doFilter(request, response, filterChain);
+        }
+    }
+
+    private void invokeWithWrappedRequest(HttpServletRequest request,
+            HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+        request.setAttribute(ALREADY_FILTERED_ATTR_NAME, Boolean.TRUE);
+        request = new DebugRequestWrapper(request);
+        try {
+            fcp.doFilter(request, response, filterChain);
+        }
+        finally {
+            request.removeAttribute(ALREADY_FILTERED_ATTR_NAME);
+        }
+    }
+
+    String formatFilters(List<Filter> filters) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Security filter chain: ");
+        if (filters == null) {
+            sb.append("no match");
+        } else if (filters.isEmpty()) {
+            sb.append("[] empty (bypassed by security='none') ");
+        } else {
+            sb.append("[\n");
+            for (Filter f : filters) {
+                sb.append("  ").append(f.getClass().getSimpleName()).append("\n");
+            }
+            sb.append("]");
+        }
+
+        return sb.toString();
+    }
+
+    private List<Filter> getFilters(HttpServletRequest request)  {
+        for (SecurityFilterChain chain : fcp.getFilterChains()) {
+            if (chain.matches(request)) {
+                return chain.getFilters();
+            }
+        }
+
+        return null;
+    }
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+    public void destroy() {
+    }
+}
+
+class DebugRequestWrapper extends HttpServletRequestWrapper {
+    private static final Logger logger = new Logger();
+
+    public DebugRequestWrapper(HttpServletRequest request) {
+        super(request);
+    }
+
+    @Override
+    public HttpSession getSession() {
+        boolean sessionExists = super.getSession(false) != null;
+        HttpSession session = super.getSession();
+
+        if (!sessionExists) {
+            logger.log("New HTTP session created: " + session.getId(), true);
+        }
+
+        return session;
+    }
+
+    @Override
+    public HttpSession getSession(boolean create) {
+        if (!create) {
+            return super.getSession(create);
+        }
+        return getSession();
+    }
+}
+
+/**
+ * Controls output for the Spring Security debug feature.
+ *
+ * @author Luke Taylor
+ * @since 3.1
+ */
+final class Logger {
+    final static Log logger = LogFactory.getLog("Spring Security Debugger");
+
+    void log(String message) {
+        log(message, false);
+    }
+
+    void log(String message, boolean dumpStack) {
+        StringBuilder output = new StringBuilder(256);
+        output.append("\n\n************************************************************\n\n");
+        output.append(message).append("\n");
+
+        if (dumpStack) {
+            StringWriter os = new StringWriter();
+            new Exception().printStackTrace(new PrintWriter(os));
+            StringBuffer buffer = os.getBuffer();
+            // Remove the exception in case it scares people.
+            int start = buffer.indexOf("java.lang.Exception");
+            buffer.replace(start, start + 19, "");
+            output.append("\nCall stack: \n").append(os.toString());
+        }
+
+        output.append("\n\n************************************************************\n\n");
+
+        logger.info(output.toString());
+    }
+}

+ 173 - 0
config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java

@@ -0,0 +1,173 @@
+/*
+ * 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.builders;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.security.web.access.channel.ChannelProcessingFilter;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
+import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
+import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
+import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
+import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
+import org.springframework.security.web.context.SecurityContextPersistenceFilter;
+import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
+import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
+import org.springframework.security.web.session.ConcurrentSessionFilter;
+import org.springframework.security.web.session.SessionManagementFilter;
+
+/**
+ * An internal use only {@link Comparator} that sorts the Security {@link Filter} instances to ensure they are in the
+ * correct order.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+
+@SuppressWarnings("serial")
+final class FilterComparator implements Comparator<Filter>, Serializable {
+    private static final int STEP = 100;
+    private Map<String,Integer> filterToOrder = new HashMap<String,Integer>();
+
+    FilterComparator() {
+        int order = 100;
+        put(ChannelProcessingFilter.class, order);
+        order += STEP;
+        put(ConcurrentSessionFilter.class, order);
+        order += STEP;
+        put(SecurityContextPersistenceFilter.class, order);
+        order += STEP;
+        put(LogoutFilter.class, order);
+        order += STEP;
+        put(X509AuthenticationFilter.class, order);
+        order += STEP;
+        put(AbstractPreAuthenticatedProcessingFilter.class, order);
+        order += STEP;
+        filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order);
+        order += STEP;
+        put(UsernamePasswordAuthenticationFilter.class, order);
+        order += STEP;
+        put(ConcurrentSessionFilter.class, order);
+        order += STEP;
+        filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order);
+        order += STEP;
+        put(DefaultLoginPageViewFilter.class, order);
+        order += STEP;
+        put(ConcurrentSessionFilter.class, order);
+        order += STEP;
+        put(DigestAuthenticationFilter.class, order);
+        order += STEP;
+        put(BasicAuthenticationFilter.class, order);
+        order += STEP;
+        put(RequestCacheAwareFilter.class, order);
+        order += STEP;
+        put(SecurityContextHolderAwareRequestFilter.class, order);
+        order += STEP;
+        put(JaasApiIntegrationFilter.class, order);
+        order += STEP;
+        put(RememberMeAuthenticationFilter.class, order);
+        order += STEP;
+        put(AnonymousAuthenticationFilter.class, order);
+        order += STEP;
+        put(SessionManagementFilter.class, order);
+        order += STEP;
+        put(ExceptionTranslationFilter.class, order);
+        order += STEP;
+        put(FilterSecurityInterceptor.class, order);
+        order += STEP;
+        put(SwitchUserFilter.class, order);
+    }
+
+    @Override
+    public int compare(Filter lhs, Filter rhs) {
+        Integer left = getOrder(lhs.getClass());
+        Integer right = getOrder(rhs.getClass());
+        return left - right;
+    }
+
+    /**
+     * Determines if a particular {@link Filter} is registered to be sorted
+     *
+     * @param filter
+     * @return
+     */
+    public boolean isRegistered(Class<? extends Filter> filter) {
+        return getOrder(filter) != null;
+    }
+
+    /**
+     * Registers a {@link Filter} to exist after a particular {@link Filter} that is already registered.
+     * @param filter the {@link Filter} to register
+     * @param afterFilter the {@link Filter} that is already registered and that {@code filter} should be placed after.
+     */
+    public void registerAfter(Class<? extends Filter> filter, Class<? extends Filter> afterFilter) {
+        Integer position = getOrder(afterFilter);
+        if(position == null) {
+            throw new IllegalArgumentException("Cannot register after unregistered Filter "+afterFilter);
+        }
+
+        put(filter, position + 1);
+    }
+
+    /**
+     * Registers a {@link Filter} to exist before a particular {@link Filter} that is already registered.
+     * @param filter the {@link Filter} to register
+     * @param beforeFilter the {@link Filter} that is already registered and that {@code filter} should be placed before.
+     */
+    public void registerBefore(Class<? extends Filter> filter, Class<? extends Filter> beforeFilter) {
+        Integer position = getOrder(beforeFilter);
+        if(position == null) {
+            throw new IllegalArgumentException("Cannot register after unregistered Filter "+beforeFilter);
+        }
+
+        put(filter, position - 1);
+    }
+
+    private void put(Class<? extends Filter> filter, int position) {
+        String className = filter.getName();
+        filterToOrder.put(className, position);
+    }
+
+    /**
+     * Gets the order of a particular {@link Filter} class taking into consideration superclasses.
+     *
+     * @param clazz the {@link Filter} class to determine the sort order
+     * @return the sort order or null if not defined
+     */
+    private Integer getOrder(Class<?> clazz) {
+        while(clazz != null) {
+            Integer result = filterToOrder.get(clazz.getName());
+            if(result != null) {
+                return result;
+            }
+            clazz = clazz.getSuperclass();
+        }
+        return null;
+    }
+}

+ 1280 - 0
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -0,0 +1,1280 @@
+/*
+ * 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.builders;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
+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.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
+import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer;
+import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
+import org.springframework.security.config.annotation.web.configurers.JeeConfigurer;
+import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
+import org.springframework.security.config.annotation.web.configurers.PortMapperConfigurer;
+import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
+import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
+import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer;
+import org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer;
+import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
+import org.springframework.security.config.annotation.web.configurers.X509Configurer;
+import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.PortMapper;
+import org.springframework.security.web.PortMapperImpl;
+import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
+import org.springframework.security.web.session.HttpSessionEventPublisher;
+import org.springframework.security.web.util.AntPathRequestMatcher;
+import org.springframework.security.web.util.AnyRequestMatcher;
+import org.springframework.security.web.util.RegexRequestMatcher;
+import org.springframework.security.web.util.RequestMatcher;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link HttpSecurity} is similar to Spring Security's XML <http> element in the namespace
+ * configuration. It allows configuring web based security for specific http requests. By default
+ * it will be applied to all requests, but can be restricted using {@link #requestMatcher(RequestMatcher)}
+ * or other similar methods.
+ *
+ * <h2>Example Usage</h2>
+ *
+ * The most basic form based configuration can be seen below. The configuration will require that any URL
+ * that is requested will require a User with the role "ROLE_USER". It also defines an in memory authentication
+ * scheme with a user that has the username "user", the password "password", and the role "ROLE_USER". For
+ * additional examples, refer to the Java Doc of individual methods on {@link HttpSecurity}.
+ *
+ * <pre>
+ * &#064;Configuration
+ * &#064;EnableWebSecurity
+ * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
+ *
+ *     &#064;Override
+ *     protected void configure(HttpSecurity http) throws Exception {
+ *         http
+ *             .authorizeUrls()
+ *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+ *                 .and()
+ *             .formLogin();
+ *     }
+ *
+ *     &#064;Override
+ *     protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+ *         auth
+ *              .inMemoryAuthentication()
+ *                   .withUser(&quot;user&quot;)
+ *                        .password(&quot;password&quot;)
+ *                        .roles(&quot;USER&quot;);
+ *     }
+ * }
+ * </pre>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see EnableWebSecurity
+ */
+public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain,HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
+    private AuthenticationManager authenticationManager;
+
+    private List<Filter> filters =  new ArrayList<Filter>();
+    private RequestMatcher requestMatcher = new AnyRequestMatcher();
+    private FilterComparator comparitor = new FilterComparator();
+
+    /**
+     * Creates a new instance
+     * @param objectPostProcessor the {@link ObjectPostProcessor} that should be used
+     * @param authenticationBuilder the {@link AuthenticationManagerBuilder} to use for additional updates
+     * @param sharedObjects the shared Objects to initialize the {@link HttpSecurity} with
+     * @see WebSecurityConfiguration
+     */
+    public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor, AuthenticationManagerBuilder authenticationBuilder, Map<Class<Object>,Object> sharedObjects) {
+        super(objectPostProcessor);
+        Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
+        setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
+        for(Map.Entry<Class<Object>, Object> entry : sharedObjects.entrySet()) {
+            setSharedObject(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Allows configuring OpenID based authentication. Multiple invocations of
+     * {@link #openidLogin()} will override previous invocations.
+     *
+     * <h2>Example Configurations</h2>
+     *
+     * A basic example accepting the defaults and not using attribute exchange:
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .openidLogin()
+     *                 .permitAll();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+     *         auth
+     *                 .inMemoryAuthentication()
+     *                     // the username must match the OpenID of the user you are
+     *                     // logging in with
+     *                     .withUser(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;)
+     *                         .password(&quot;password&quot;)
+     *                         .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * A more advanced example demonstrating using attribute exchange and
+     * providing a custom AuthenticationUserDetailsService that will make any
+     * user that authenticates a valid user.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .openidLogin()
+     *                 .loginPage(&quot;/login&quot;)
+     *                 .permitAll()
+     *                 .authenticationUserDetailsService(new AutoProvisioningUserDetailsService())
+     *                     .attributeExchange(&quot;https://www.google.com/.*&quot;)
+     *                         .attribute(&quot;email&quot;)
+     *                             .type(&quot;http://axschema.org/contact/email&quot;)
+     *                             .required(true)
+     *                             .and()
+     *                         .attribute(&quot;firstname&quot;)
+     *                             .type(&quot;http://axschema.org/namePerson/first&quot;)
+     *                             .required(true)
+     *                             .and()
+     *                         .attribute(&quot;lastname&quot;)
+     *                             .type(&quot;http://axschema.org/namePerson/last&quot;)
+     *                             .required(true)
+     *                             .and()
+     *                         .and()
+     *                     .attributeExchange(&quot;.*yahoo.com.*&quot;)
+     *                         .attribute(&quot;email&quot;)
+     *                             .type(&quot;http://schema.openid.net/contact/email&quot;)
+     *                             .required(true)
+     *                             .and()
+     *                         .attribute(&quot;fullname&quot;)
+     *                             .type(&quot;http://axschema.org/namePerson&quot;)
+     *                             .required(true)
+     *                             .and()
+     *                         .and()
+     *                     .attributeExchange(&quot;.*myopenid.com.*&quot;)
+     *                         .attribute(&quot;email&quot;)
+     *                             .type(&quot;http://schema.openid.net/contact/email&quot;)
+     *                             .required(true)
+     *                             .and()
+     *                         .attribute(&quot;fullname&quot;)
+     *                             .type(&quot;http://schema.openid.net/namePerson&quot;)
+     *                             .required(true);
+     *     }
+     * }
+     *
+     * public class AutoProvisioningUserDetailsService implements
+     *         AuthenticationUserDetailsService&lt;OpenIDAuthenticationToken&gt; {
+     *     public UserDetails loadUserDetails(OpenIDAuthenticationToken token) throws UsernameNotFoundException {
+     *         return new User(token.getName(), &quot;NOTUSED&quot;, AuthorityUtils.createAuthorityList(&quot;ROLE_USER&quot;));
+     *     }
+     * }
+     * </pre>
+     *
+     * @return the {@link OpenIDLoginConfigurer} for further customizations.
+     *
+     * @throws Exception
+     * @see OpenIDLoginConfigurer
+     */
+    public OpenIDLoginConfigurer<HttpSecurity> openidLogin() throws Exception {
+        return apply(new OpenIDLoginConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Allows configuring of Session Management. Multiple invocations of
+     * {@link #sessionManagement()} will override previous invocations.
+     *
+     * <h2>Example Configuration</h2>
+     *
+     * The following configuration demonstrates how to enforce that only a
+     * single instance of a user is authenticated at a time. If a user
+     * authenticates with the username "user" without logging out and an attempt
+     * to authenticate with "user" is made the first session will be forcibly
+     * terminated and sent to the "/login?expired" URL.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class SessionManagementSecurityConfig extends
+     *         WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .anyRequest().hasRole(&quot;USER&quot;)
+     *                 .and()
+     *            .formLogin()
+     *                 .permitAll()
+     *                 .and()
+     *            .sessionManagement()
+     *                 .maximumSessions(1)
+     *                 .expiredUrl(&quot;/login?expired&quot;);
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth.
+     *             inMemoryAuthentication()
+     *                 .withUser(&quot;user&quot;)
+     *                     .password(&quot;password&quot;)
+     *                     .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * When using {@link SessionManagementConfigurer#maximumSessions(int)}, do
+     * not forget to configure {@link HttpSessionEventPublisher} for the
+     * application to ensure that expired sessions are cleaned up.
+     *
+     * In a web.xml this can be configured using the following:
+     *
+     * <pre>
+     * &lt;listener&gt;
+     *      &ltlistener-class&gt;org.springframework.security.web.session.HttpSessionEventPublisher&lt;/listener-class&gt;
+     * &lt/listener>
+     * </pre>
+     *
+     * Alternatively,
+     * {@link AbstractSecurityWebApplicationInitializer#enableHttpSessionEventPublisher()}
+     * could return true.
+     *
+     * @return the {@link SessionManagementConfigurer} for further
+     *         customizations
+     * @throws Exception
+     */
+    public SessionManagementConfigurer<HttpSecurity> sessionManagement() throws Exception {
+        return apply(new SessionManagementConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Allows configuring a {@link PortMapper} that is available from
+     * {@link HttpSecurity#getSharedObject(Class)}. Other provided
+     * {@link SecurityConfigurer} objects use this configured
+     * {@link PortMapper} as a default {@link PortMapper} when redirecting from
+     * HTTP to HTTPS or from HTTPS to HTTP (for example when used in combination
+     * with {@link #requiresChannel()}. By default Spring Security uses a
+     * {@link PortMapperImpl} which maps the HTTP port 8080 to the HTTPS port
+     * 8443 and the HTTP port of 80 to the HTTPS port of 443.
+     *
+     * <h2>Example Configuration</h2>
+     *
+     * The following configuration will ensure that redirects within Spring
+     * Security from HTTP of a port of 9090 will redirect to HTTPS port of 9443
+     * and the HTTP port of 80 to the HTTPS port of 443.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class PortMapperSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin()
+     *                 .permitAll()
+     *                 .and()
+     *                 // Example portMapper() configuration
+     *                 .portMapper()
+     *                     .http(9090).mapsTo(9443)
+     *                     .http(80).mapsTo(443);
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+     *         auth
+     *             .inMemoryAuthentication()
+     *                 .withUser(&quot;user&quot;)
+     *                     .password(&quot;password&quot;)
+     *                     .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * @return the {@link PortMapperConfigurer} for further customizations
+     * @throws Exception
+     * @see {@link #requiresChannel()}
+     */
+    public PortMapperConfigurer<HttpSecurity> portMapper() throws Exception {
+        return apply(new PortMapperConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Configures container based based pre authentication. In this case,
+     * authentication is managed by the Servlet Container.
+     *
+     * <h2>Example Configuration</h2>
+     *
+     * The following configuration will use the principal found on the
+     * {@link HttpServletRequest} and if the user is in the role "ROLE_USER" or
+     * "ROLE_ADMIN" will add that to the resulting {@link Authentication}.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class JeeSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             // Example jee() configuration
+     *             .jee()
+     *                 .mappableRoles(&quot;ROLE_USER&quot;, &quot;ROLE_ADMIN&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * Developers wishing to use pre authentication with the container will need
+     * to ensure their web.xml configures the security constraints. For example,
+     * the web.xml (there is no equivalent Java based configuration supported by
+     * the Servlet specification) might look like:
+     *
+     * <pre>
+     * &lt;login-config&gt;
+     *     &lt;auth-method&gt;FORM&lt;/auth-method&gt;
+     *     &lt;form-login-config&gt;
+     *         &lt;form-login-page&gt;/login&lt;/form-login-page&gt;
+     *         &lt;form-error-page&gt;/login?error&lt;/form-error-page&gt;
+     *     &lt;/form-login-config&gt;
+     * &lt;/login-config&gt;
+     *
+     * &lt;security-role&gt;
+     *     &lt;role-name&gt;ROLE_USER&lt;/role-name&gt;
+     * &lt;/security-role&gt;
+     * &lt;security-constraint&gt;
+     *     &lt;web-resource-collection&gt;
+     *     &lt;web-resource-name&gt;Public&lt;/web-resource-name&gt;
+     *         &lt;description&gt;Matches unconstrained pages&lt;/description&gt;
+     *         &lt;url-pattern&gt;/login&lt;/url-pattern&gt;
+     *         &lt;url-pattern&gt;/logout&lt;/url-pattern&gt;
+     *         &lt;url-pattern&gt;/resources/*&lt;/url-pattern&gt;
+     *     &lt;/web-resource-collection&gt;
+     * &lt;/security-constraint&gt;
+     * &lt;security-constraint&gt;
+     *     &lt;web-resource-collection&gt;
+     *         &lt;web-resource-name&gt;Secured Areas&lt;/web-resource-name&gt;
+     *         &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+     *     &lt;/web-resource-collection&gt;
+     *     &lt;auth-constraint&gt;
+     *         &lt;role-name&gt;ROLE_USER&lt;/role-name&gt;
+     *     &lt;/auth-constraint&gt;
+     * &lt;/security-constraint&gt;
+     * </pre>
+     *
+     * Last you will need to configure your container to contain the user with the
+     * correct roles. This configuration is specific to the Servlet Container, so consult
+     * your Servlet Container's documentation.
+     *
+     * @return the {@link JeeConfigurer} for further customizations
+     * @throws Exception
+     */
+    public JeeConfigurer<HttpSecurity> jee() throws Exception {
+        return apply(new JeeConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Configures X509 based pre authentication.
+     *
+     * <h2>Example Configuration</h2>
+     *
+     * The following configuration will attempt to extract the username from
+     * the X509 certificate. Remember that the Servlet Container will need to be
+     * configured to request client certificates in order for this to work.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class X509SecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             // Example x509() configuration
+     *             .x509();
+     *     }
+     * }
+     * </pre>
+     *
+     * @return the {@link X509Configurer} for further customizations
+     * @throws Exception
+     */
+    public X509Configurer<HttpSecurity> x509() throws Exception {
+        return apply(new X509Configurer<HttpSecurity>());
+    }
+
+    /**
+     * Allows configuring of Remember Me authentication. Multiple invocations of
+     * {@link #rememberMe()} will override previous invocations.
+     *
+     * <h2>Example Configuration</h2>
+     *
+     * The following configuration demonstrates how to allow token based remember me
+     * authentication. Upon authenticating if the HTTP parameter named "remember-me" exists,
+     * then the user will be remembered even after their {@link javax.servlet.http.HttpSession} expires.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class RememberMeSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;);
+     *     }
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin()
+     *                 .permitAll()
+     *                 .and()
+     *              // Example Remember Me Configuration
+     *             .rememberMe();
+     *     }
+     * }
+     * </pre>
+     *
+     * @return the {@link RememberMeConfigurer} for further customizations
+     * @throws Exception
+     */
+    public RememberMeConfigurer<HttpSecurity> rememberMe() throws Exception {
+        return apply(new RememberMeConfigurer<HttpSecurity>());
+    }
+
+
+    /**
+     * 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()}.
+     *
+     * <h2>Example Configurations</h2>
+     *
+     * The most basic example is to configure all URLs to require the role "ROLE_USER". The
+     * configuration below requires authentication to every URL and will grant access to
+     * both the user "admin" and "user".
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;)
+     *                        .and()
+     *                   .withUser(&quot;adminr&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;ADMIN&quot;,&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * We can also configure multiple URLs. The configuration below requires authentication to every URL
+     * and will grant access to URLs starting with /admin/ to only the "admin" user. All other URLs either
+     * user can access.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;)
+     *                        .and()
+     *                   .withUser(&quot;adminr&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;ADMIN&quot;,&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * Note that the matchers are considered in order. Therefore, the following is invalid because the first
+     * matcher matches every request and will never get to the second mapping:
+     *
+     * <pre>
+     * http
+     *     .authorizeUrls()
+     *         .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *         .antMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
+     * </pre>
+     *
+     * @see #requestMatcher(RequestMatcher)
+     *
+     * @return
+     * @throws Exception
+     */
+    public ExpressionUrlAuthorizationConfigurer<HttpSecurity> authorizeUrls() throws Exception {
+        return apply(new ExpressionUrlAuthorizationConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Allows configuring the Request Cache. For example, a protected page (/protected) may be requested prior
+     * to authentication. The application will redirect the user to a login page. After authentication, Spring
+     * Security will redirect the user to the originally requested protected page (/protected). This is
+     * automatically applied when using {@link WebSecurityConfigurerAdapter}.
+     *
+     * @return the {@link RequestCacheConfigurer} for further customizations
+     * @throws Exception
+     */
+    public RequestCacheConfigurer<HttpSecurity> requestCache() throws Exception {
+        return apply(new RequestCacheConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Allows configuring exception handling. This is automatically applied when using
+     * {@link WebSecurityConfigurerAdapter}.
+     *
+     * @return the {@link ExceptionHandlingConfigurer} for further customizations
+     * @throws Exception
+     */
+    public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
+        return apply(new ExceptionHandlingConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Sets up management of the {@link SecurityContext} on the
+     * {@link SecurityContextHolder} between {@link HttpServletRequest}'s. This is automatically
+     * applied when using {@link WebSecurityConfigurerAdapter}.
+     *
+     * @return the {@link SecurityContextConfigurer} for further customizations
+     * @throws Exception
+     */
+    public SecurityContextConfigurer<HttpSecurity> securityContext() throws Exception {
+        return apply(new SecurityContextConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Integrates the {@link HttpServletRequest} methods with the values found
+     * on the {@link SecurityContext}. This is automatically applied when using
+     * {@link WebSecurityConfigurerAdapter}.
+     *
+     * @return the {@link ServletApiConfigurer} for further customizations
+     * @throws Exception
+     */
+    public ServletApiConfigurer<HttpSecurity> servletApi() throws Exception {
+        return apply(new ServletApiConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Provides logout support. This is automatically applied when using
+     * {@link WebSecurityConfigurerAdapter}. The default is that accessing
+     * the URL "/logout" will log the user out by invalidating the HTTP Session,
+     * cleaning up any {@link #rememberMe()} authentication that was configured,
+     * clearing the {@link SecurityContextHolder}, and then redirect to
+     * "/login?success".
+     *
+     * <h2>Example Custom Configuration</h2>
+     *
+     * The following customization to log out when the URL "/custom-logout" is
+     * invoked. Log out will remove the cookie named "remove", not invalidate the
+     * HttpSession, clear the SecurityContextHolder, and upon completion redirect
+     * to "/logout-success".
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class LogoutSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin()
+     *                 .and()
+     *             // sample logout customization
+     *             .logout()
+     *                 .logout()
+     *                    .deleteCookies("remove")
+     *                    .invalidateHttpSession(false)
+     *                    .logoutUrl("/custom-logout")
+     *                    .logoutSuccessUrl("/logout-success");
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * @return
+     * @throws Exception
+     */
+    public LogoutConfigurer<HttpSecurity> logout() throws Exception {
+        return apply(new LogoutConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Allows configuring how an anonymous user is represented. This is automatically applied
+     * when used in conjunction with {@link WebSecurityConfigurerAdapter}. By default anonymous
+     * users will be represented with an {@link org.springframework.security.authentication.AnonymousAuthenticationToken} and contain the role
+     * "ROLE_ANONYMOUS".
+     *
+     * <h2>Example Configuration</h2
+     *
+     * The following configuration demonstrates how to specify that anonymous users should contain
+     * the role "ROLE_ANON" instead.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class AnononymousSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin()
+     *                 .and()
+     *             // sample anonymous customization
+     *             .anonymous()
+     *                 .authorities("ROLE_ANON");
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * The following demonstrates how to represent anonymous users as null. Note that this can cause
+     * {@link NullPointerException} in code that assumes anonymous authentication is enabled.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class AnononymousSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin()
+     *                 .and()
+     *             // sample anonymous customization
+     *             .anonymous()
+     *                 .disabled();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * @return
+     * @throws Exception
+     */
+    public AnonymousConfigurer<HttpSecurity> anonymous() throws Exception {
+        return apply(new AnonymousConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Specifies to support form based authentication. If
+     * {@link FormLoginConfigurer#loginPage(String)} is not specified a
+     * default login page will be generated.
+     *
+     * <h2>Example Configurations</h2>
+     *
+     * The most basic configuration defaults to automatically generating a login
+     * page at the URL "/login", redirecting to "/login?error" for
+     * authentication failure. The details of the login page can be found on
+     * {@link FormLoginConfigurer#loginPage(String)}
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * The configuration below demonstrates customizing the defaults.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin()
+     *                    .usernameParameter("j_username") // default is username
+     *                    .passwordParameter("j_password") // default is password
+     *                    .loginPage("/authentication/login") // default is /login with an HTTP get
+     *                    .failureUrl("/authentication/login?failed") // default is /login?error
+     *                    .loginProcessingUrl("/authentication/login/process"); // default is /login with an HTTP post
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * @see FormLoginConfigurer#loginPage(String)
+     *
+     * @return
+     * @throws Exception
+     */
+    public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
+        return apply(new FormLoginConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * 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.
+     *
+     * <h2>Example Configuration</h2>
+     *
+     * The example below demonstrates how to require HTTPs for every request. Only requiring HTTPS
+     * for some requests is supported, but not recommended since an application that allows for HTTP
+     * introduces many security vulnerabilities. For one such example, read about
+     * <a href="http://en.wikipedia.org/wiki/Firesheep">Firesheep</a>.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class ChannelSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+     *                 .and()
+     *             .formLogin()
+     *                 .and()
+     *             .channelSecurity()
+     *                 .anyRequest().requiresSecure();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *              .inMemoryAuthentication()
+     *                   .withUser(&quot;user&quot;)
+     *                        .password(&quot;password&quot;)
+     *                        .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     *
+     * @return the {@link ChannelSecurityConfigurer} for further customizations
+     * @throws Exception
+     */
+    public ChannelSecurityConfigurer<HttpSecurity> requiresChannel() throws Exception {
+        return apply(new ChannelSecurityConfigurer<HttpSecurity>());
+    }
+
+    /**
+     * Configures HTTP Basic authentication. Multiple infocations of
+     * {@link #httpBasic()} will override previous invocations.
+     *
+     * <h2>Example Configuration</h2>
+     *
+     * The example below demonstrates how to configure HTTP Basic authentication
+     * for an application. The default realm is "Spring Security Application",
+     * but can be customized using
+     * {@link HttpBasicConfigurer#realmName(String)}.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class HttpBasicSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and()
+     *                 .httpBasic();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *             .inMemoryAuthentication()
+     *                 .withUser(&quot;user&quot;)
+     *                     .password(&quot;password&quot;)
+     *                     .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * @return the {@link HttpBasicConfigurer} for further customizations
+     * @throws Exception
+     */
+    public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
+        return apply(new HttpBasicConfigurer<HttpSecurity>());
+    }
+
+    @Override
+    protected void beforeConfigure() throws Exception {
+        this.authenticationManager = getAuthenticationRegistry().build();
+    }
+
+    @Override
+    protected DefaultSecurityFilterChain performBuild() throws Exception {
+        Collections.sort(filters,comparitor);
+        return new DefaultSecurityFilterChain(requestMatcher, filters);
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.HttpBuilder#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)
+     */
+    @Override
+    public HttpSecurity authenticationProvider(AuthenticationProvider authenticationProvider) {
+        getAuthenticationRegistry().authenticationProvider(authenticationProvider);
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.HttpBuilder#userDetailsService(org.springframework.security.core.userdetails.UserDetailsService)
+     */
+    @Override
+    public HttpSecurity userDetailsService(UserDetailsService userDetailsService) throws Exception {
+        getAuthenticationRegistry().userDetailsService(userDetailsService);
+        return this;
+    }
+
+    private AuthenticationManagerBuilder getAuthenticationRegistry() {
+        return getSharedObject(AuthenticationManagerBuilder.class);
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.HttpBuilder#addFilterAfter(javax.servlet.Filter, java.lang.Class)
+     */
+    @Override
+    public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
+        comparitor.registerAfter(filter.getClass(), afterFilter);
+        return addFilter(filter);
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.HttpBuilder#addFilterBefore(javax.servlet.Filter, java.lang.Class)
+     */
+    @Override
+    public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
+        comparitor.registerBefore(filter.getClass(), beforeFilter);
+        return addFilter(filter);
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.HttpBuilder#addFilter(javax.servlet.Filter)
+     */
+    @Override
+    public HttpSecurity addFilter(Filter filter) {
+        Class<? extends Filter> filterClass = filter.getClass();
+        if(!comparitor.isRegistered(filterClass)) {
+            throw new IllegalArgumentException(
+                    "The Filter class " + filterClass.getName()
+                            + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
+        }
+        this.filters.add(filter);
+        return this;
+    }
+
+    /**
+     * Allows specifying which {@link HttpServletRequest} instances this
+     * {@link HttpSecurity} will be invoked on.  This method allows for
+     * easily invoking the {@link HttpSecurity} for multiple
+     * different {@link RequestMatcher} instances. If only a single {@link RequestMatcher}
+     * is necessary consider using {@link #antMatcher(String)},
+     * {@link #regexMatcher(String)}, or {@link #requestMatcher(RequestMatcher)}.
+     *
+     * <p>
+     * Invoking {@link #requestMatchers()} will override previous invocations of
+     * {@link #requestMatchers()}, {@link #antMatcher(String)}, {@link #regexMatcher(String)},
+     * and {@link #requestMatcher(RequestMatcher)}.
+     * </p>
+     *
+     * <h3>Example Configurations</h3>
+     *
+     * The following configuration enables the {@link HttpSecurity} for URLs that
+     * begin with "/api/" or "/oauth/".
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .requestMatchers()
+     *                 .antMatchers("/api/**","/oauth/**")
+     *                 .and()
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and()
+     *                 .httpBasic();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *             .inMemoryAuthentication()
+     *                 .withUser(&quot;user&quot;)
+     *                     .password(&quot;password&quot;)
+     *                     .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * The configuration below is the same as the previous configuration.
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .requestMatchers()
+     *                 .antMatchers("/api/**")
+     *                 .antMatchers("/oauth/**")
+     *                 .and()
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and()
+     *                 .httpBasic();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *             .inMemoryAuthentication()
+     *                 .withUser(&quot;user&quot;)
+     *                     .password(&quot;password&quot;)
+     *                     .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * 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/**".
+     *
+     * <pre>
+     * &#064;Configuration
+     * &#064;EnableWebSecurity
+     * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+     *
+     *     &#064;Override
+     *     protected void configure(HttpSecurity http) throws Exception {
+     *         http
+     *             .requestMatchers()
+     *                 .antMatchers("/api/**")
+     *                 .and()
+     *             .requestMatchers()
+     *                 .antMatchers("/oauth/**")
+     *                 .and()
+     *             .authorizeUrls()
+     *                 .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and()
+     *                 .httpBasic();
+     *     }
+     *
+     *     &#064;Override
+     *     protected void registerAuthentication(AuthenticationManagerBuilder auth)
+     *             throws Exception {
+     *         auth
+     *             .inMemoryAuthentication()
+     *                 .withUser(&quot;user&quot;)
+     *                     .password(&quot;password&quot;)
+     *                     .roles(&quot;USER&quot;);
+     *     }
+     * }
+     * </pre>
+     *
+     * @return the {@link RequestMatcherConfigurer} for further customizations
+     */
+    public RequestMatcherConfigurer requestMatchers() {
+        return new RequestMatcherConfigurer();
+    }
+
+    /**
+     * Allows configuring the {@link HttpSecurity} to only be invoked when
+     * matching the provided {@link RequestMatcher}. If more advanced configuration is
+     * necessary, consider using {@link #requestMatchers()}.
+     *
+     * <p>
+     * Invoking {@link #requestMatcher(RequestMatcher)} will override previous invocations of
+     * {@link #requestMatchers()}, {@link #antMatcher(String)}, {@link #regexMatcher(String)},
+     * and {@link #requestMatcher(RequestMatcher)}.
+     * </p>
+     *
+     * @param requestMatcher the {@link RequestMatcher} to use (i.e. new AntPathRequestMatcher("/admin/**","GET") )
+     * @return the {@link HttpSecurity} for further customizations
+     * @see #requestMatchers()
+     * @see #antMatcher(String)
+     * @see #regexMatcher(String)
+     */
+    public HttpSecurity requestMatcher(RequestMatcher requestMatcher) {
+        this.requestMatcher = requestMatcher;
+        return this;
+    }
+
+    /**
+     * Allows configuring the {@link HttpSecurity} to only be invoked when
+     * matching the provided ant pattern. If more advanced configuration is
+     * necessary, consider using {@link #requestMatchers()} or
+     * {@link #requestMatcher(RequestMatcher)}.
+     *
+     * <p>
+     * Invoking {@link #antMatcher(String)} will override previous invocations of
+     * {@link #requestMatchers()}, {@link #antMatcher(String)}, {@link #regexMatcher(String)},
+     * and {@link #requestMatcher(RequestMatcher)}.
+     * </p>
+     *
+     * @param antPattern the Ant Pattern to match on (i.e. "/admin/**")
+     * @return the {@link HttpSecurity} for further customizations
+     * @see AntPathRequestMatcher
+     */
+    public HttpSecurity antMatcher(String antPattern) {
+        return requestMatcher(new AntPathRequestMatcher(antPattern));
+    }
+
+    /**
+     * Allows configuring the {@link HttpSecurity} to only be invoked when
+     * matching the provided regex pattern. If more advanced configuration is
+     * necessary, consider using {@link #requestMatchers()} or
+     * {@link #requestMatcher(RequestMatcher)}.
+     *
+     * <p>
+     * Invoking {@link #regexMatcher(String)} will override previous invocations of
+     * {@link #requestMatchers()}, {@link #antMatcher(String)}, {@link #regexMatcher(String)},
+     * and {@link #requestMatcher(RequestMatcher)}.
+     * </p>
+     *
+     * @param pattern the Regular Expression to match on (i.e. "/admin/.+")
+     * @return the {@link HttpSecurity} for further customizations
+     * @see RegexRequestMatcher
+     */
+    public HttpSecurity regexMatcher(String pattern) {
+        return requestMatcher(new RegexRequestMatcher(pattern, null));
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.HttpBuilder#getAuthenticationManager()
+     */
+    @Override
+    public AuthenticationManager getAuthenticationManager() {
+        return authenticationManager;
+    }
+
+    /**
+     * Allows mapping HTTP requests that this {@link HttpSecurity} will be used for
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    public final class RequestMatcherConfigurer extends AbstractRequestMatcherConfigurer<HttpSecurity,RequestMatcherConfigurer,DefaultSecurityFilterChain> {
+
+        protected RequestMatcherConfigurer chainRequestMatchers(List<RequestMatcher> requestMatchers) {
+            requestMatcher(new OrRequestMatcher(requestMatchers));
+            return this;
+        }
+
+        /**
+         * Return the {@link HttpSecurity} for further customizations
+         *
+         * @return the {@link HttpSecurity} for further customizations
+         */
+        public HttpSecurity and() {
+            return HttpSecurity.this;
+        }
+
+        private RequestMatcherConfigurer(){}
+    }
+
+    /**
+     * Internal {@link RequestMatcher} instance used by {@link RequestMatcher}
+     * that will match if any of the passed in {@link RequestMatcher} instances
+     * match.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    private static final class OrRequestMatcher implements RequestMatcher {
+        private final List<RequestMatcher> requestMatchers;
+
+        private OrRequestMatcher(List<RequestMatcher> requestMatchers) {
+            this.requestMatchers = requestMatchers;
+        }
+
+        @Override
+        public boolean matches(HttpServletRequest request) {
+            for(RequestMatcher matcher : requestMatchers) {
+                if(matcher.matches(request)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}

+ 309 - 0
config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java

@@ -0,0 +1,309 @@
+/*
+ * 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.builders;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.access.expression.SecurityExpressionHandler;
+import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer;
+import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
+import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
+import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+import org.springframework.security.web.firewall.DefaultHttpFirewall;
+import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.util.RequestMatcher;
+import org.springframework.util.Assert;
+import org.springframework.web.filter.DelegatingFilterProxy;
+
+/**
+ * <p>
+ * The {@link WebSecurity} is created by {@link WebSecurityConfiguration}
+ * to create the {@link FilterChainProxy} known as the Spring Security Filter
+ * Chain (springSecurityFilterChain). The springSecurityFilterChain is the
+ * {@link Filter} that the {@link DelegatingFilterProxy} delegates to.
+ * </p>
+ *
+ * <p>
+ * Customizations to the {@link WebSecurity} can be made by creating a
+ * {@link WebSecurityConfigurer} or more likely by overriding
+ * {@link WebSecurityConfigurerAdapter}.
+ * </p>
+ *
+ * @see EnableWebSecurity
+ * @see WebSecurityConfiguration
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class WebSecurity extends
+        AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter> {
+    private final Log logger = LogFactory.getLog(getClass());
+
+    private final List<RequestMatcher> ignoredRequests = new ArrayList<RequestMatcher>();
+
+    private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders =
+            new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();
+
+    private final IgnoredRequestConfigurer ignoredRequestRegistry =
+            new IgnoredRequestConfigurer();
+
+    private FilterSecurityInterceptor filterSecurityInterceptor;
+
+    private HttpFirewall httpFirewall;
+
+    private boolean debugEnabled;
+
+    private WebInvocationPrivilegeEvaluator privilegeEvaluator;
+
+    private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
+
+    /**
+     * Creates a new instance
+     * @see WebSecurityConfiguration
+     */
+    public WebSecurity() {
+    }
+
+    /**
+     * <p>
+     * Allows adding {@link RequestMatcher} instances that should that Spring
+     * Security should ignore. Web Security provided by Spring Security
+     * (including the {@link SecurityContext}) will not be available on
+     * {@link HttpServletRequest} that match. Typically the requests that are
+     * registered should be that of only static resources. For requests that are
+     * dynamic, consider mapping the request to allow all users instead.
+     * </p>
+     *
+     * Example Usage:
+     *
+     * <pre>
+     * webSecurityBuilder
+     *     .ignoring()
+     *         // ignore all URLs that start with /resources/ or /static/
+     *         .antMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;);
+     * </pre>
+     *
+     * Alternatively this will accomplish the same result:
+     *
+     * <pre>
+     * webSecurityBuilder
+     *     .ignoring()
+     *         // ignore all URLs that start with /resources/ or /static/
+     *         .antMatchers(&quot;/resources/**&quot;)
+     *         .antMatchers(&quot;/static/**&quot;);
+     * </pre>
+     *
+     * Multiple invocations of ignoring() are also additive, so the following is
+     * also equivalent to the previous two examples:
+     *
+     * Alternatively this will accomplish the same result:
+     *
+     * <pre>
+     * webSecurityBuilder
+     *     .ignoring()
+     *         // ignore all URLs that start with /resources/
+     *         .antMatchers(&quot;/resources/**&quot;);
+     * webSecurityBuilder
+     *     .ignoring()
+     *         // ignore all URLs that start with /static/
+     *         .antMatchers(&quot;/static/**&quot;);
+     * // now both URLs that start with /resources/ and /static/ will be ignored
+     * </pre>
+     *
+     * @return the {@link IgnoredRequestConfigurer} to use for registering request
+     *         that should be ignored
+     */
+    public IgnoredRequestConfigurer ignoring() {
+        return ignoredRequestRegistry;
+    }
+
+    /**
+     * Allows customizing the {@link HttpFirewall}. The default is
+     * {@link DefaultHttpFirewall}.
+     *
+     * @param httpFirewall the custom {@link HttpFirewall}
+     * @return the {@link WebSecurity} for further customizations
+     */
+    public WebSecurity httpFirewall(HttpFirewall httpFirewall) {
+        this.httpFirewall = httpFirewall;
+        return this;
+    }
+
+    /**
+     * Controls debugging support for Spring Security.
+     *
+     * @param debugEnabled
+     *            if true, enables debug support with Spring Security. Default
+     *            is false.
+     *
+     * @return the {@link WebSecurity} for further customization.
+     * @see EnableWebSecurity#debug()
+     */
+    public WebSecurity debug(boolean debugEnabled) {
+        this.debugEnabled = debugEnabled;
+        return this;
+    }
+
+    /**
+     * <p>
+     * Adds builders to create {@link SecurityFilterChain} instances.
+     * </p>
+     *
+     * <p>
+     * Typically this method is invoked automatically within the framework from
+     * {@link WebSecurityConfigurerAdapter#init(WebSecurity)}
+     * </p>
+     *
+     * @param securityFilterChainBuilder
+     *            the builder to use to create the {@link SecurityFilterChain}
+     *            instances
+     * @return the {@link WebSecurity} for further customizations
+     */
+    public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
+        this.securityFilterChainBuilders.add(securityFilterChainBuilder);
+        return this;
+    }
+
+    /**
+     * Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is
+     * null, then a {@link DefaultWebInvocationPrivilegeEvaluator} will be
+     * created when {@link #setSecurityInterceptor(FilterSecurityInterceptor)}
+     * is non null.
+     *
+     * @param privilegeEvaluator
+     *            the {@link WebInvocationPrivilegeEvaluator} to use
+     * @return the {@link WebSecurity} for further customizations
+     */
+    public WebSecurity privilegeEvaluator(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
+        this.privilegeEvaluator = privilegeEvaluator;
+        return this;
+    }
+
+    /**
+     * Set the {@link SecurityExpressionHandler} to be used. If this is null,
+     * then a {@link DefaultWebSecurityExpressionHandler} will be used.
+     *
+     * @param expressionHandler
+     *            the {@link SecurityExpressionHandler} to use
+     * @return the {@link WebSecurity} for further customizations
+     */
+    public WebSecurity expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
+        Assert.notNull(expressionHandler, "expressionHandler cannot be null");
+        this.expressionHandler = expressionHandler;
+        return this;
+    }
+
+    /**
+     * Gets the {@link SecurityExpressionHandler} to be used.
+     * @return
+     */
+    public SecurityExpressionHandler<FilterInvocation> getExpressionHandler() {
+        return expressionHandler;
+    }
+
+    /**
+     * Gets the {@link WebInvocationPrivilegeEvaluator} to be used.
+     * @return
+     */
+    public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
+        if(privilegeEvaluator != null) {
+            return privilegeEvaluator;
+        }
+        return filterSecurityInterceptor == null ? null : new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor);
+    }
+
+    /**
+     * Sets the {@link FilterSecurityInterceptor}. This is typically invoked by {@link WebSecurityConfigurerAdapter}.
+     * @param securityInterceptor the {@link FilterSecurityInterceptor} to use
+     */
+    public void setSecurityInterceptor(FilterSecurityInterceptor securityInterceptor) {
+        this.filterSecurityInterceptor = securityInterceptor;
+    }
+
+    @Override
+    protected Filter performBuild() throws Exception {
+        Assert.state(!securityFilterChainBuilders.isEmpty(), "At least one SecurityFilterBuilder needs to be specified. Invoke FilterChainProxyBuilder.securityFilterChains");
+        int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
+        List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(chainSize);
+        for(RequestMatcher ignoredRequest : ignoredRequests) {
+            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
+        }
+        for(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
+            securityFilterChains.add(securityFilterChainBuilder.build());
+        }
+        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
+        if(httpFirewall != null) {
+            filterChainProxy.setFirewall(httpFirewall);
+        }
+        filterChainProxy.afterPropertiesSet();
+
+        Filter result = filterChainProxy;
+        if(debugEnabled) {
+            logger.warn("\n\n" +
+                    "********************************************************************\n" +
+                    "**********        Security debugging is enabled.       *************\n" +
+                    "**********    This may include sensitive information.  *************\n" +
+                    "**********      Do not use in a production system!     *************\n" +
+                    "********************************************************************\n\n");
+            result = new DebugFilter(filterChainProxy);
+        }
+        return result;
+    }
+
+    /**
+     * Allows registering {@link RequestMatcher} instances that should be
+     * ignored by Spring Security.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    public final class IgnoredRequestConfigurer extends AbstractRequestMatcherConfigurer<WebSecurity,IgnoredRequestConfigurer,Filter> {
+
+        @Override
+        protected IgnoredRequestConfigurer chainRequestMatchers(List<RequestMatcher> requestMatchers) {
+            ignoredRequests.addAll(requestMatchers);
+            return this;
+        }
+
+        /**
+         * Returns the {@link WebSecurity} to be returned for chaining.
+         */
+        @Override
+        public WebSecurity and() {
+            return WebSecurity.this;
+        }
+
+        private IgnoredRequestConfigurer(){}
+    }
+}

+ 87 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java

@@ -0,0 +1,87 @@
+/*
+ * 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.configuration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
+import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
+
+/**
+ * Add this annotation to an {@code @Configuration} class to have the Spring Security
+ * configuration defined in any {@link WebSecurityConfigurer} or more likely by extending the
+ * {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
+ *
+ * <pre class="code">
+ * &#064;Configuration
+ * &#064;EnableWebSecurity
+ * public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+ *
+ *    &#064;Override
+ *    public void configure(WebSecurity web) throws Exception {
+ *        web
+ *            .ignoring()
+ *                // Spring Security should completely ignore URLs starting with /resources/
+ *                .antMatchers("/resources/**");
+ *    }
+ *
+ *    &#064;Override
+ *    protected void configure(HttpSecurity http) throws Exception {
+ *        http
+ *            .authorizeUrls()
+ *                .antMatchers("/public/**").permitAll()
+ *                .anyRequest().hasRole("USER")
+ *                .and()
+ *            // Possibly more configuration ...
+ *            .formLogin() // enable form based log in
+ *                // set permitAll for all URLs associated with Form Login
+ *               .permitAll();
+ *    }
+ *
+ *    &#064;Override
+ *    protected void registerAuthentication(AuthenticationManagerBuilder auth) {
+ *        registry
+ *            // enable in memory based authentication with a user named "user" and "admin"
+ *            .inMemoryAuthentication()
+ *                .withUser("user").password("password").roles("USER").and()
+ *                .withUser("admin").password("password").roles("USER", "ADMIN");
+ *    }
+ *
+ *    // Possibly more overridden methods ...
+ * }
+ * </pre>
+ *
+ * @see WebSecurityConfigurer
+ * @see WebSecurityConfigurerAdapter
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(value={java.lang.annotation.ElementType.TYPE})
+@Documented
+@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class})
+public @interface EnableWebSecurity {
+
+    /**
+     * Controls debugging support for Spring Security. Default is false.
+     * @return if true, enables debug support with Spring Security
+     */
+    boolean debug() default false;
+}

+ 187 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java

@@ -0,0 +1,187 @@
+/*
+ * 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.configuration;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.Filter;
+
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.context.annotation.ImportAware;
+import org.springframework.core.OrderComparator;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.security.access.expression.SecurityExpressionHandler;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that
+ * performs the web based security for Spring Security. It then exports the
+ * necessary beans. Customizations can be made to {@link WebSecurity} by
+ * extending {@link WebSecurityConfigurerAdapter} and exposing it as a
+ * {@link Configuration} or implementing {@link WebSecurityConfigurer} and
+ * exposing it as a {@link Configuration}. This configuration is imported when
+ * using {@link EnableWebSecurity}.
+ *
+ * @see EnableWebSecurity
+ * @see WebSecurity
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+@Configuration
+public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
+    private final WebSecurity webSecurity = new WebSecurity();
+
+    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
+
+    private ClassLoader beanClassLoader;
+
+    @Bean
+    @DependsOn("springSecurityFilterChain")
+    public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
+        return webSecurity.getExpressionHandler();
+    }
+
+    /**
+     * Creates the Spring Security Filter Chain
+     * @return
+     * @throws Exception
+     */
+    @Bean(name="springSecurityFilterChain")
+    public Filter springSecurityFilterChain() throws Exception {
+        boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty();
+        if(!hasConfigurers) {
+            throw new IllegalStateException("At least one non-null instance of "+ WebSecurityConfigurer.class.getSimpleName()+" must be exposed as a @Bean when using @EnableWebSecurity. Hint try extending "+ WebSecurityConfigurerAdapter.class.getSimpleName());
+        }
+        return webSecurity.build();
+    }
+
+    /**
+     * Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP tag support.
+     * @return the {@link WebInvocationPrivilegeEvaluator}
+     * @throws Exception
+     */
+    @Bean
+    @DependsOn("springSecurityFilterChain")
+    public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
+        return webSecurity.getPrivilegeEvaluator();
+    }
+
+    /**
+     * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to create the web configuration.
+     *
+     * @param webSecurityConfigurers the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to create the web configuration
+     * @throws Exception
+     */
+    @Autowired(required = false)
+    public void setFilterChainProxySecurityConfigurer(
+            List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception {
+        Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
+
+        Integer previousOrder = null;
+        for(SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
+            Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
+            if(previousOrder != null && previousOrder.equals(order)) {
+                throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order + " was already used, so it cannot be used on " + config + " too.");
+            }
+            previousOrder = order;
+        }
+        for(SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
+            webSecurity.apply(webSecurityConfigurer);
+        }
+        this.webSecurityConfigurers = webSecurityConfigurers;
+    }
+
+
+    /**
+     * A custom verision of the Spring provided AnnotationAwareOrderComparator
+     * that uses {@link AnnotationUtils#findAnnotation(Class, Class)} to look on
+     * super class instances for the {@link Order} annotation.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    private static class AnnotationAwareOrderComparator extends OrderComparator {
+        private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
+
+        @Override
+        protected int getOrder(Object obj) {
+            return lookupOrder(obj);
+        }
+
+        private static int lookupOrder(Object obj) {
+            if (obj instanceof Ordered) {
+                return ((Ordered) obj).getOrder();
+            }
+            if (obj != null) {
+                Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
+                Order order = AnnotationUtils.findAnnotation(clazz,Order.class);
+                if (order != null) {
+                    return order.value();
+                }
+            }
+            return Ordered.LOWEST_PRECEDENCE;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.springframework.core.type.AnnotationMetadata)
+     */
+    @Override
+    public void setImportMetadata(AnnotationMetadata importMetadata) {
+        Map<String, Object> enableWebSecurityAttrMap = importMetadata.getAnnotationAttributes(EnableWebSecurity.class.getName());
+        AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
+        if(enableWebSecurityAttrs == null) {
+            // search parent classes
+            Class<?> currentClass = ClassUtils.resolveClassName(importMetadata.getClassName(), beanClassLoader);
+            for(Class<?> classToInspect = currentClass ;classToInspect != null; classToInspect = classToInspect.getSuperclass()) {
+                EnableWebSecurity enableWebSecurityAnnotation = AnnotationUtils.findAnnotation(classToInspect, EnableWebSecurity.class);
+                if(enableWebSecurityAnnotation == null) {
+                    continue;
+                }
+                enableWebSecurityAttrMap = AnnotationUtils
+                        .getAnnotationAttributes(enableWebSecurityAnnotation);
+                enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
+            }
+        }
+        boolean debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
+        this.webSecurity.debug(debugEnabled);
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
+     */
+    @Override
+    public void setBeanClassLoader(ClassLoader classLoader) {
+        this.beanClassLoader = classLoader;
+    }
+}

+ 326 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java

@@ -0,0 +1,326 @@
+/*
+ * 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.configuration;
+
+
+import javax.servlet.Filter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
+import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+
+/**
+ * Provides a convenient base class for creating a {@link WebSecurityConfigurer}
+ * instance. The implementation allows customization by overriding methods.
+ *
+ * @see EnableWebSecurity
+ *
+ * @author Rob Winch
+ */
+public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer<Filter,WebSecurity> {
+    private final Log logger = LogFactory.getLog(getClass());
+
+    @Autowired
+    private ApplicationContext context;
+
+    @Autowired(required=false)
+    private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
+        @Override
+        public <T> T postProcess(T object) {
+            throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @EnableWebSecurity and @Configuration");
+        }
+    };
+
+    private final AuthenticationManagerBuilder authenticationBuilder = new AuthenticationManagerBuilder();
+    private final AuthenticationManagerBuilder parentAuthenticationBuilder = new AuthenticationManagerBuilder() {
+        @Override
+        public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
+            authenticationBuilder.eraseCredentials(eraseCredentials);
+            return super.eraseCredentials(eraseCredentials);
+        }
+
+    };
+    private boolean disableAuthenticationRegistration;
+    private boolean authenticationManagerInitialized;
+    private AuthenticationManager authenticationManager;
+    private HttpSecurity http;
+    private boolean disableDefaults;
+
+    /**
+     * Creates an instance with the default configuration enabled.
+     */
+    protected WebSecurityConfigurerAdapter() {
+        this(false);
+    }
+
+    /**
+     * Creates an instance which allows specifying if the default configuration
+     * should be enabled. Disabling the default configuration should be
+     * considered more advanced usage as it requires more understanding of how
+     * the framework is implemented.
+     *
+     * @param disableDefaults
+     *            true if the default configuration should be enabled, else
+     *            false
+     */
+    protected WebSecurityConfigurerAdapter(boolean disableDefaults) {
+        this.disableDefaults = disableDefaults;
+    }
+
+    /**
+     * Used by the default implementation of {@link #authenticationManager()} to attempt to obtain an
+     * {@link AuthenticationManager}. If overridden, the {@link AuthenticationManagerBuilder} should be used to specify
+     * the {@link AuthenticationManager}. The resulting {@link AuthenticationManager}
+     * will be exposed as a Bean as will the last populated {@link UserDetailsService} that is created with the
+     * {@link AuthenticationManagerBuilder}. The {@link UserDetailsService} will also automatically be populated on
+     * {@link HttpSecurity#getSharedObject(Class)} for use with other {@link SecurityContextConfigurer}
+     * (i.e. RememberMeConfigurer )
+     *
+     * <p>For example, the following configuration could be used to register
+     * in memory authentication that exposes an in memory {@link UserDetailsService}:</p>
+     *
+     * <pre>
+     * &#064;Override
+     * protected void registerAuthentication(AuthenticationManagerBuilder auth) {
+     *     registry
+     *         // enable in memory based authentication with a user named "user" and "admin"
+     *         .inMemoryAuthentication()
+     *             .withUser("user").password("password").roles("USER").and()
+     *             .withUser("admin").password("password").roles("USER", "ADMIN");
+     * }
+     * </pre>
+     *
+     * @param auth the {@link AuthenticationManagerBuilder} to use
+     * @throws Exception
+     */
+    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+        this.disableAuthenticationRegistration = true;
+    }
+
+    /**
+     * Creates the {@link HttpSecurity} or returns the current instance
+     *
+     * @return the {@link HttpSecurity}
+     * @throws Exception
+     */
+    protected final HttpSecurity getHttp() throws Exception {
+        if(http != null) {
+            return http;
+        }
+
+        authenticationBuilder.objectPostProcessor(objectPostProcessor);
+        parentAuthenticationBuilder.objectPostProcessor(objectPostProcessor);
+
+        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
+        parentAuthenticationBuilder.authenticationEventPublisher(eventPublisher);
+
+        AuthenticationManager authenticationManager = authenticationManager();
+        authenticationBuilder.parentAuthenticationManager(authenticationManager);
+        http = new HttpSecurity(objectPostProcessor,authenticationBuilder, parentAuthenticationBuilder.getSharedObjects());
+        http.setSharedObject(UserDetailsService.class, userDetailsService());
+        if(!disableDefaults) {
+            http
+                .exceptionHandling().and()
+                .sessionManagement().and()
+                .securityContext().and()
+                .requestCache().and()
+                .anonymous().and()
+                .servletApi().and()
+                .apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
+                .logout();
+        }
+        configure(http);
+        return http;
+    }
+
+    /**
+     * Override this method to expose the {@link AuthenticationManager} from
+     * {@link #registerAuthentication(AuthenticationManagerBuilder)} to be exposed as
+     * a Bean. For example:
+     *
+     * <pre>
+     * &#064;Bean(name name="myAuthenticationManager")
+     * &#064;Override
+     * public AuthenticationManager authenticationManagerBean() throws Exception {
+     *     return super.authenticationManagerBean();
+     * }
+     * </pre>
+     *
+     * @return the {@link AuthenticationManager}
+     * @throws Exception
+     */
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return new AuthenticationManagerDelegator(authenticationBuilder);
+    }
+
+    /**
+     * Gets the {@link AuthenticationManager} to use. The default strategy is if
+     * {@link #registerAuthentication(AuthenticationManagerBuilder)} method is
+     * overridden to use the {@link AuthenticationManagerBuilder} that was passed in.
+     * Otherwise, autowire the {@link AuthenticationManager} by type.
+     *
+     * @return
+     * @throws Exception
+     */
+    protected AuthenticationManager authenticationManager() throws Exception {
+        if(!authenticationManagerInitialized) {
+            registerAuthentication(parentAuthenticationBuilder);
+            if(disableAuthenticationRegistration) {
+                try {
+                    authenticationManager = context.getBean(AuthenticationManager.class);
+                } catch(NoSuchBeanDefinitionException e) {
+                    logger.debug("The AuthenticationManager was not found. This is ok for now as it may not be required.",e);
+                }
+            } else {
+                authenticationManagerInitialized = true;
+                authenticationManager = parentAuthenticationBuilder.build();
+            }
+            authenticationManagerInitialized = true;
+        }
+        return authenticationManager;
+    }
+
+    /**
+     * Override this method to expose a {@link UserDetailsService} created from
+     * {@link #registerAuthentication(AuthenticationManagerBuilder)} as a bean. In
+     * general only the following override should be done of this method:
+     *
+     * <pre>
+     * &#064;Bean(name = "myUserDetailsService") // any or no name specified is allowed
+     * &#064;Override
+     * public UserDetailsService userDetailsServiceBean() throws Exception {
+     *     return super.userDetailsServiceBean();
+     * }
+     * </pre>
+     *
+     * To change the instance returned, developers should change
+     * {@link #userDetailsService()} instead
+     * @return
+     * @throws Exception
+     * @see {@link #userDetailsService()}
+     */
+    public UserDetailsService userDetailsServiceBean() throws Exception {
+        return userDetailsService();
+    }
+
+    /**
+     * Allows modifying and accessing the {@link UserDetailsService} from
+     * {@link #userDetailsServiceBean()()} without interacting with the
+     * {@link ApplicationContext}. Developers should override this method when
+     * changing the instance of {@link #userDetailsServiceBean()}.
+     *
+     * @return
+     */
+    protected UserDetailsService userDetailsService() {
+        return parentAuthenticationBuilder.getDefaultUserDetailsService();
+    }
+
+    @Override
+    public void init(WebSecurity web) throws Exception {
+        HttpSecurity http = getHttp();
+        FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
+        web
+            .addSecurityFilterChainBuilder(http)
+            .setSecurityInterceptor(securityInterceptor);
+    }
+
+    /**
+     * Override this method to configure {@link WebSecurity}. For
+     * example, if you wish to ignore certain requests.
+     */
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+    }
+
+    /**
+     * Override this method to configure the {@link HttpSecurity}.
+     * Typically subclasses should not invoke this method by calling super
+     * as it may override their configuration. The default configuration is:
+     *
+     * <pre>
+     * http
+     *     .authorizeUrls()
+     *         .anyRequest().authenticated().and()
+     *     .formLogin().and()
+     *     .httpBasic();
+     * </pre>
+     *
+     * @param http
+     *            the {@link HttpSecurity} to modify
+     * @throws Exception
+     *             if an error occurs
+     */
+    protected void configure(HttpSecurity http) throws Exception {
+        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
+
+        http
+            .authorizeUrls()
+                .anyRequest().authenticated()
+                .and()
+            .formLogin().and()
+            .httpBasic();
+    }
+
+    /**
+     * Delays the use of the {@link AuthenticationManager} build from the
+     * {@link AuthenticationManagerBuilder} to ensure that it has been fully
+     * configured.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    static final class AuthenticationManagerDelegator implements AuthenticationManager {
+        private AuthenticationManagerBuilder delegateBuilder;
+        private AuthenticationManager delegate;
+        private final Object delegateMonitor = new Object();
+
+        AuthenticationManagerDelegator(AuthenticationManagerBuilder authentication) {
+            this.delegateBuilder = authentication;
+        }
+
+        @Override
+        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+            if(delegate != null) {
+                return delegate.authenticate(authentication);
+            }
+
+            synchronized(delegateMonitor) {
+                if (delegate == null) {
+                    delegate = this.delegateBuilder.getObject();
+                    this.delegateBuilder = null;
+                }
+            }
+
+            return delegate.authenticate(authentication);
+        }
+    }
+}

+ 319 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java

@@ -0,0 +1,319 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.PortMapper;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
+import org.springframework.security.web.authentication.RememberMeServices;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+
+/**
+ * Base class for confuring {@link AbstractAuthenticationFilterConfigurer}. This is intended for internal use only.
+ *
+ * @see FormLoginConfigurer
+ * @see OpenIDLoginConfigurer
+ *
+ * @param T refers to "this" for returning the current configurer
+ * @param F refers to the {@link AbstractAuthenticationProcessingFilter} that is being built
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public abstract class AbstractAuthenticationFilterConfigurer<B  extends HttpSecurityBuilder<B>,T extends AbstractAuthenticationFilterConfigurer<B,T, F>, F extends AbstractAuthenticationProcessingFilter> extends AbstractHttpConfigurer<B> {
+
+    private final F authFilter;
+
+    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
+
+    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
+
+    private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;
+
+    private boolean customLoginPage;
+    private String loginPage;
+    private String loginProcessingUrl;
+
+    private AuthenticationFailureHandler failureHandler;
+
+    private boolean permitAll;
+
+    private String failureUrl;
+
+    /**
+     * Creates a new instance
+     * @param authenticationFilter the {@link AbstractAuthenticationProcessingFilter} to use
+     * @param defaultLoginProcessingUrl the default URL to use for {@link #loginProcessingUrl(String)}
+     */
+    protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) {
+        this.authFilter = authenticationFilter;
+        loginUrl("/login");
+        failureUrl("/login?error");
+        loginProcessingUrl(defaultLoginProcessingUrl);
+        this.customLoginPage = false;
+    }
+
+    /**
+     * Specifies where users will go after authenticating successfully if they
+     * have not visited a secured page prior to authenticating. This is a
+     * shortcut for calling {@link #defaultSuccessUrl(String)}.
+     *
+     * @param defaultSuccessUrl
+     *            the default success url
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T defaultSuccessUrl(String defaultSuccessUrl) {
+        return defaultSuccessUrl(defaultSuccessUrl, false);
+    }
+
+    /**
+     * Specifies where users will go after authenticating successfully if they
+     * have not visited a secured page prior to authenticating or
+     * {@code alwaysUse} is true. This is a shortcut for calling
+     * {@link #successHandler(AuthenticationSuccessHandler)}.
+     *
+     * @param defaultSuccessUrl
+     *            the default success url
+     * @param alwaysUse
+     *            true if the {@code defaultSuccesUrl} should be used after
+     *            authentication despite if a protected page had been previously
+     *            visited
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse) {
+        SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
+        handler.setDefaultTargetUrl(defaultSuccessUrl);
+        handler.setAlwaysUseDefaultTargetUrl(alwaysUse);
+        return successHandler(handler);
+    }
+
+    /**
+     * Specifies the URL used to log in. If the request matches the URL and is an HTTP POST, the
+     * {@link UsernamePasswordAuthenticationFilter} will attempt to authenticate
+     * the request. Otherwise, if the request matches the URL the user will be sent to the login form.
+     *
+     * @param loginUrl the URL used to perform authentication
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T loginUrl(String loginUrl) {
+        loginProcessingUrl(loginUrl);
+        return loginPage(loginUrl);
+    }
+
+    /**
+     * Specifies the URL to validate the credentials.
+     *
+     * @param loginProcessingUrl
+     *            the URL to validate username and password
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public T loginProcessingUrl(String loginProcessingUrl) {
+        this.loginProcessingUrl = loginProcessingUrl;
+        authFilter.setFilterProcessesUrl(loginProcessingUrl);
+        return getSelf();
+    }
+
+    /**
+     * Specifies a custom {@link AuthenticationDetailsSource}. The default is {@link WebAuthenticationDetailsSource}.
+     *
+     * @param authenticationDetailsSource the custom {@link AuthenticationDetailsSource}
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
+        this.authenticationDetailsSource = authenticationDetailsSource;
+        return getSelf();
+    }
+
+    /**
+     * Specifies the {@link AuthenticationSuccessHandler} to be used. The
+     * default is {@link SavedRequestAwareAuthenticationSuccessHandler} with no
+     * additional properites set.
+     *
+     * @param successHandler
+     *            the {@link AuthenticationSuccessHandler}.
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T successHandler(AuthenticationSuccessHandler successHandler) {
+        this.successHandler = successHandler;
+        return getSelf();
+    }
+
+    /**
+     * Equivalent of invoking permitAll(true)
+     * @return
+     */
+    public final T permitAll() {
+        return permitAll(true);
+    }
+
+    /**
+     * Ensures the urls for {@link #failureUrl(String)} and
+     * {@link #loginUrl(String)} are granted access to any user.
+     *
+     * @param permitAll true to grant access to the URLs false to skip this step
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T permitAll(boolean permitAll) {
+        this.permitAll = permitAll;
+        return getSelf();
+    }
+
+    /**
+     * The URL to send users if authentication fails. This is a shortcut for
+     * invoking {@link #failureHandler(AuthenticationFailureHandler)}. The
+     * default is "/login?error".
+     *
+     * @param authenticationFailureUrl
+     *            the URL to send users if authentication fails (i.e.
+     *            "/login?error").
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T failureUrl(String authenticationFailureUrl) {
+        T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl));
+        this.failureUrl = authenticationFailureUrl;
+        return result;
+    }
+
+    /**
+     * Specifies the {@link AuthenticationFailureHandler} to use when
+     * authentication fails. The default is redirecting to "/login?error" using
+     * {@link SimpleUrlAuthenticationFailureHandler}
+     *
+     * @param authenticationFailureHandler
+     *            the {@link AuthenticationFailureHandler} to use when
+     *            authentication fails.
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public final T failureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
+        this.failureUrl = null;
+        this.failureHandler = authenticationFailureHandler;
+        return getSelf();
+    }
+
+    @Override
+    public void init(B http) throws Exception {
+        if(permitAll) {
+            PermitAllSupport.permitAll(http, loginPage, loginProcessingUrl, failureUrl);
+        }
+        http.setSharedObject(AuthenticationEntryPoint.class, postProcess(authenticationEntryPoint));
+    }
+
+    @Override
+    public void configure(B http) throws Exception {
+        PortMapper portMapper = http.getSharedObject(PortMapper.class);
+        if(portMapper != null) {
+            authenticationEntryPoint.setPortMapper(portMapper);
+        }
+
+        authFilter.setAuthenticationManager(http.getAuthenticationManager());
+        authFilter.setAuthenticationSuccessHandler(successHandler);
+        authFilter.setAuthenticationFailureHandler(failureHandler);
+        if(authenticationDetailsSource != null) {
+            authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
+        }
+        SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
+        if(sessionAuthenticationStrategy != null) {
+            authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
+        }
+        RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
+        if(rememberMeServices != null) {
+            authFilter.setRememberMeServices(rememberMeServices);
+        }
+        F filter = postProcess(authFilter);
+        http.addFilter(filter);
+    }
+
+    /**
+     * <p>
+     * Specifies the URL to send users to if login is required. If used with
+     * {@link WebSecurityConfigurerAdapter} a default login page will be
+     * generated when this attribute is not specified.
+     * </p>
+     *
+     * <p>
+     * If a URL is specified or this is not being used in conjuction with
+     * {@link WebSecurityConfigurerAdapter}, users are required to process the
+     * specified URL to generate a login page.
+     * </p>
+     */
+    protected T loginPage(String loginPage) {
+        this.loginPage = loginPage;
+        this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage);
+        this.customLoginPage = true;
+        return getSelf();
+    }
+
+    /**
+    *
+    * @return true if a custom login page has been specified, else false
+    */
+   public final boolean isCustomLoginPage() {
+       return customLoginPage;
+   }
+
+   /**
+    * Gets the Authentication Filter
+    * @return
+    */
+   protected final F getAuthenticationFilter() {
+       return authFilter;
+   }
+
+   /**
+    * Gets the login page
+    * @return the login page
+    */
+   protected final String getLoginPage() {
+       return loginPage;
+   }
+
+   /**
+    * Gets the URL to submit an authentication request to (i.e. where
+    * username/password must be submitted)
+    *
+    * @return the URL to submit an authentication request to
+    */
+   protected final String getLoginProcessingUrl() {
+       return loginProcessingUrl;
+   }
+
+   /**
+    * Gets the URL to send users to if authentication fails
+    * @return
+    */
+   protected final String getFailureUrl() {
+       return failureUrl;
+   }
+
+
+    @SuppressWarnings("unchecked")
+    private T getSelf() {
+        return (T) this;
+    }
+}

+ 33 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java

@@ -0,0 +1,33 @@
+/*
+ * 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.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+
+/**
+ * Adds a convenient base class for {@link SecurityConfigurer} instances that
+ * operate on {@link HttpSecurity}.
+ *
+ * @author Rob Winch
+ *
+ */
+abstract class AbstractHttpConfigurer<B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
+
+}

+ 181 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractInterceptUrlConfigurer.java

@@ -0,0 +1,181 @@
+/*
+ * 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 java.util.List;
+
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.vote.AffirmativeBased;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+
+/**
+ * A base class for configuring the {@link FilterSecurityInterceptor}.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ *     <li>{@link FilterSecurityInterceptor}</li>
+ * </ul>
+  *
+ * <h2>Shared Objects Created</h2>
+ *
+ * The following shared objects are populated to allow other {@link SecurityConfigurer}'s to customize:
+ * <ul>
+ *     <li>{@link FilterSecurityInterceptor}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ *     <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
+ * </ul>
+ *
+ * @param <H> the type of {@link HttpSecurityBuilder} that is being configured
+ * @param <C> the type of object that is changed
+ * @param <R> the type of object that is changed for the {@link AbstractRequestMatcherMappingConfigurer}
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see ExpressionUrlAuthorizationConfigurer
+ * @see UrlAuthorizationConfigurer
+ */
+abstract class AbstractInterceptUrlConfigurer<H extends HttpSecurityBuilder<H>,C,R> extends
+        AbstractRequestMatcherMappingConfigurer<H,R,DefaultSecurityFilterChain> implements
+        SecurityConfigurer<DefaultSecurityFilterChain,H> {
+    private Boolean filterSecurityInterceptorOncePerRequest;
+
+    private AccessDecisionManager accessDecisionManager;
+
+    /**
+     * Allows setting the {@link AccessDecisionManager}. If none is provided, a default {@l AccessDecisionManager} is
+     * created.
+     *
+     * @param accessDecisionManager the {@link AccessDecisionManager} to use
+     * @return  the {@link AbstractInterceptUrlConfigurer} for further customization
+     */
+    public C accessDecisionManager(
+            AccessDecisionManager accessDecisionManager) {
+        this.accessDecisionManager = accessDecisionManager;
+        return getSelf();
+    }
+
+    /**
+     * Allows setting if the {@link FilterSecurityInterceptor} should be only applied once per request (i.e. if the
+     * filter intercepts on a forward, should it be applied again).
+     *
+     * @param filterSecurityInterceptorOncePerRequest if the {@link FilterSecurityInterceptor} should be only applied
+     *                                                once per request
+     * @return  the {@link AbstractInterceptUrlConfigurer} for further customization
+     */
+    public C filterSecurityInterceptorOncePerRequest(
+            boolean filterSecurityInterceptorOncePerRequest) {
+        this.filterSecurityInterceptorOncePerRequest = filterSecurityInterceptorOncePerRequest;
+        return getSelf();
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource();
+        if(metadataSource == null) {
+            return;
+        }
+        FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(metadataSource, http.getAuthenticationManager());
+        if(filterSecurityInterceptorOncePerRequest != null) {
+            securityInterceptor.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
+        }
+        http.addFilter(securityInterceptor);
+        http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
+    }
+
+    /**
+     * Subclasses should implement this method to provide a {@link FilterInvocationSecurityMetadataSource} for the
+     * {@link FilterSecurityInterceptor}.
+     *
+     * @return the {@link FilterInvocationSecurityMetadataSource} to set on the {@link FilterSecurityInterceptor}.
+     *         Cannot be null.
+     */
+    abstract FilterInvocationSecurityMetadataSource createMetadataSource();
+
+    /**
+     * Subclasses should implement this method to provide the {@link AccessDecisionVoter} instances used to create the
+     * default {@link AccessDecisionManager}
+     *
+     * @return the {@link AccessDecisionVoter} instances used to create the
+     *         default {@link AccessDecisionManager}
+     */
+    @SuppressWarnings("rawtypes")
+    abstract List<AccessDecisionVoter> getDecisionVoters();
+
+    /**
+     * Creates the default {@code AccessDecisionManager}
+     * @return the default {@code AccessDecisionManager}
+     */
+    private AccessDecisionManager createDefaultAccessDecisionManager() {
+        return new AffirmativeBased(getDecisionVoters());
+    }
+
+    /**
+     * If currently null, creates a default {@link AccessDecisionManager} using
+     * {@link #createDefaultAccessDecisionManager()}. Otherwise returns the {@link AccessDecisionManager}.
+     *
+     * @return the {@link AccessDecisionManager} to use
+     */
+    private AccessDecisionManager getAccessDecisionManager() {
+        if (accessDecisionManager == null) {
+            accessDecisionManager = createDefaultAccessDecisionManager();
+        }
+        return accessDecisionManager;
+    }
+
+    /**
+     * Creates the {@link FilterSecurityInterceptor}
+     *
+     * @param metadataSource the {@link FilterInvocationSecurityMetadataSource} to use
+     * @param authenticationManager the {@link AuthenticationManager} to use
+     * @return the {@link FilterSecurityInterceptor}
+     * @throws Exception
+     */
+    private FilterSecurityInterceptor createFilterSecurityInterceptor(FilterInvocationSecurityMetadataSource metadataSource,
+                                                                      AuthenticationManager authenticationManager) throws Exception {
+        FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
+        securityInterceptor.setSecurityMetadataSource(metadataSource);
+        securityInterceptor.setAccessDecisionManager(getAccessDecisionManager());
+        securityInterceptor.setAuthenticationManager(authenticationManager);
+        securityInterceptor.afterPropertiesSet();
+        return securityInterceptor;
+    }
+
+    /**
+     * Returns a reference to the current object with a single suppression of
+     * the type
+     *
+     * @return a reference to the current object
+     */
+    @SuppressWarnings("unchecked")
+    private C getSelf() {
+        return (C) this;
+    }
+}

+ 144 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurer.java

@@ -0,0 +1,144 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer;
+import org.springframework.security.web.util.RequestMatcher;
+
+/**
+ * A base class for registering {@link RequestMatcher}'s. For example, it might allow for specifying which
+ * {@link RequestMatcher} require a certain level of authorization.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ *
+ * @param <B> The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer}
+ * @param <C> The object that is returned or Chained after creating the RequestMatcher
+ * @param <O> The Object being built by Builder B
+ *
+ * @see ChannelSecurityConfigurer
+ * @see UrlAuthorizationConfigurer
+ * @see ExpressionUrlAuthorizationConfigurer
+ */
+public abstract class AbstractRequestMatcherMappingConfigurer<B extends SecurityBuilder<O>,C,O> extends AbstractRequestMatcherConfigurer<B,C,O> {
+    private List<UrlMapping> urlMappings = new ArrayList<UrlMapping>();
+    private List<RequestMatcher> unmappedMatchers;
+
+    /**
+     * Gets the {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)}. May be empty.
+     *
+     * @return the {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)}
+     */
+    final List<UrlMapping> getUrlMappings() {
+        return urlMappings;
+    }
+
+    /**
+     * Adds a {@link UrlMapping} added by subclasses in
+     * {@link #chainRequestMatchers(java.util.List)} and resets the unmapped
+     * {@link RequestMatcher}'s.
+     *
+     * @param urlMapping
+     *            {@link UrlMapping} the mapping to add
+     */
+    final void addMapping(UrlMapping urlMapping) {
+        this.unmappedMatchers = null;
+        this.urlMappings.add(urlMapping);
+    }
+
+    /**
+     * Marks the {@link RequestMatcher}'s as unmapped and then calls {@link #chainRequestMatchersInternal(List)}.
+     *
+     * @param requestMatchers the {@link RequestMatcher} instances that were created
+     * @return the chained Object for the subclass which allows association of something else to the
+     *         {@link RequestMatcher}
+     */
+    protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
+        this.unmappedMatchers = requestMatchers;
+        return chainRequestMatchersInternal(requestMatchers);
+    }
+
+    /**
+     * Subclasses should implement this method for returning the object that is chained to the creation of the
+     * {@link RequestMatcher} instances.
+     *
+     * @param requestMatchers the {@link RequestMatcher} instances that were created
+     * @return the chained Object for the subclass which allows association of something else to the
+     *         {@link RequestMatcher}
+     */
+     protected abstract C chainRequestMatchersInternal(List<RequestMatcher> requestMatchers);
+
+    /**
+     * Adds a {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)} at a particular
+     * index.
+     *
+     * @param index the index to add a {@link UrlMapping}
+     * @param urlMapping {@link UrlMapping} the mapping to add
+     */
+    final void addMapping(int index, UrlMapping urlMapping) {
+        this.urlMappings.add(index, urlMapping);
+    }
+
+    /**
+     * Creates the mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances
+     *
+     * @return the mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances. Cannot
+     *         be null.
+     */
+    final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> createRequestMap() {
+        if(unmappedMatchers != null) {
+            throw new IllegalStateException("An incomplete mapping was found for " + unmappedMatchers +". Try completing it with something like requestUrls().<something>.hasRole('USER')");
+        }
+
+        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
+        for (UrlMapping mapping : getUrlMappings()) {
+            RequestMatcher matcher = mapping.getRequestMatcher();
+            Collection<ConfigAttribute> configAttrs = mapping.getConfigAttrs();
+            requestMap.put(matcher,configAttrs);
+        }
+        return requestMap;
+    }
+
+    /**
+     * A mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances
+     */
+    static final class UrlMapping {
+        private RequestMatcher requestMatcher;
+        private Collection<ConfigAttribute> configAttrs;
+
+        UrlMapping(RequestMatcher requestMatcher,
+                Collection<ConfigAttribute> configAttrs) {
+            this.requestMatcher = requestMatcher;
+            this.configAttrs = configAttrs;
+        }
+
+        public RequestMatcher getRequestMatcher() {
+            return requestMatcher;
+        }
+
+        public Collection<ConfigAttribute> getConfigAttrs() {
+            return configAttrs;
+        }
+    }
+}
+

+ 165 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java

@@ -0,0 +1,165 @@
+/*
+ * 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 java.util.List;
+import java.util.UUID;
+
+import org.springframework.security.authentication.AnonymousAuthenticationProvider;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+
+/**
+ * Configures Anonymous authentication (i.e. populate an {@link Authentication} that represents an anonymous user
+ * instead of having a null value) for an {@link HttpSecurity}. Specifically this will configure an
+ * {@link AnonymousAuthenticationFilter} and an {@link AnonymousAuthenticationProvider}. All properties have reasonable
+ * defaults, so no additional configuration is required other than applying this {@link SecurityConfigurer}.
+ *
+ * @author  Rob Winch
+ * @since  3.2
+ */
+public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,H> {
+    private String key;
+    private AuthenticationProvider authenticationProvider;
+    private AnonymousAuthenticationFilter authenticationFilter;
+    private Object principal = "anonymousUser";
+    private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#anonymous()
+     */
+    public AnonymousConfigurer() {
+    }
+
+    /**
+     * Disables anonymous authentication.
+     *
+     * @return the {@link HttpSecurity} since no further customization of anonymous authentication would be
+     *         meaningful.
+     */
+    @SuppressWarnings("unchecked")
+    public H disable() {
+        getBuilder().removeConfigurer(getClass());
+        return getBuilder();
+    }
+
+    /**
+     * Sets the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
+     * key.
+     *
+     * @param key the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
+     *            key.
+     * @return  the {@link AnonymousConfigurer} for further customization of anonymous authentication
+     */
+    public AnonymousConfigurer<H> key(String key) {
+        this.key = key;
+        return this;
+    }
+
+    /**
+     * Sets the principal for {@link Authentication} objects of anonymous users
+     *
+     * @param principal used for the {@link Authentication} object of anonymous users
+     * @return  the {@link AnonymousConfigurer} for further customization of anonymous authentication
+     */
+    public AnonymousConfigurer<H> principal(Object principal) {
+        this.principal = principal;
+        return this;
+    }
+
+    /**
+     * Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
+     *
+     * @param authorities Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
+     * @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
+     */
+    public AnonymousConfigurer<H> authorities(List<GrantedAuthority> authorities) {
+        this.authorities = authorities;
+        return this;
+    }
+
+    /**
+     * Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
+     *
+     * @param authorities Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for
+     *                    anonymous users (i.e. "ROLE_ANONYMOUS")
+     * @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
+     */
+    public AnonymousConfigurer<H> authorities(String... authorities) {
+        return authorities(AuthorityUtils.createAuthorityList(authorities));
+    }
+
+    /**
+     * Sets the {@link AuthenticationProvider} used to validate an anonymous user. If this is set, no attributes
+     * on the {@link AnonymousConfigurer} will be set on the {@link AuthenticationProvider}.
+     *
+     * @param authenticationProvider the {@link AuthenticationProvider} used to validate an anonymous user. Default is
+     *                               {@link AnonymousAuthenticationProvider}
+     *
+     * @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
+     */
+    public AnonymousConfigurer<H> authenticationProvider(AuthenticationProvider authenticationProvider) {
+        this.authenticationProvider = authenticationProvider;
+        return this;
+    }
+
+    /**
+     * Sets the {@link AnonymousAuthenticationFilter} used to populate an anonymous user. If this is set, no attributes
+     * on the {@link AnonymousConfigurer} will be set on the {@link AnonymousAuthenticationFilter}.
+     *
+     * @param authenticationFilter the {@link AnonymousAuthenticationFilter} used to populate an anonymous user.
+     *
+     * @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
+     */
+    public AnonymousConfigurer<H> authenticationFilter(AnonymousAuthenticationFilter authenticationFilter) {
+        this.authenticationFilter = authenticationFilter;
+        return this;
+    }
+
+    @Override
+    public void init(H http) throws Exception {
+        if(authenticationProvider == null) {
+            authenticationProvider = new AnonymousAuthenticationProvider(getKey());
+        }
+        if(authenticationFilter == null) {
+            authenticationFilter = new AnonymousAuthenticationFilter(getKey(), principal, authorities);
+        }
+        authenticationProvider = postProcess(authenticationProvider);
+        http.authenticationProvider(authenticationProvider);
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        authenticationFilter.afterPropertiesSet();
+        http.addFilter(authenticationFilter);
+    }
+
+    private String getKey() {
+        if(key == null) {
+            key = UUID.randomUUID().toString();
+        }
+        return key;
+    }
+}

+ 168 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java

@@ -0,0 +1,168 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.PortMapper;
+import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
+import org.springframework.security.web.access.channel.ChannelProcessingFilter;
+import org.springframework.security.web.access.channel.ChannelProcessor;
+import org.springframework.security.web.access.channel.InsecureChannelProcessor;
+import org.springframework.security.web.access.channel.RetryWithHttpEntryPoint;
+import org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint;
+import org.springframework.security.web.access.channel.SecureChannelProcessor;
+import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
+import org.springframework.security.web.util.RequestMatcher;
+
+/**
+ * Adds channel security (i.e. requires HTTPS or HTTP) to an application. In order for
+ * {@link ChannelSecurityConfigurer} to be useful, at least one {@link RequestMatcher} should be mapped to HTTP
+ * or HTTPS.
+ *
+ * <p>
+ * By default an {@link InsecureChannelProcessor} and a {@link SecureChannelProcessor} will be registered.
+ * </p>
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ *     <li>{@link ChannelProcessingFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared objects are created.
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ *     <li>{@link PortMapper} is used to create the default {@link ChannelProcessor} instances</li>
+ * </ul>
+ *
+ * @param <H> the type of {@link HttpSecurityBuilder} that is being configured
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> extends
+        AbstractRequestMatcherMappingConfigurer<H,ChannelSecurityConfigurer<H>.RequiresChannelUrl,DefaultSecurityFilterChain> {
+    private ChannelProcessingFilter channelFilter = new ChannelProcessingFilter();
+    private LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>>();
+    private List<ChannelProcessor> channelProcessors;
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#requiresChannel()
+     */
+    public ChannelSecurityConfigurer() {
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        ChannelDecisionManagerImpl channelDecisionManager = new ChannelDecisionManagerImpl();
+        channelDecisionManager.setChannelProcessors(getChannelProcessors(http));
+        channelDecisionManager = postProcess(channelDecisionManager);
+
+        channelFilter.setChannelDecisionManager(channelDecisionManager);
+
+        DefaultFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource =
+                new DefaultFilterInvocationSecurityMetadataSource(requestMap);
+        channelFilter.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
+
+        channelFilter = postProcess(channelFilter);
+        http.addFilter(channelFilter);
+    }
+
+    /**
+     * Sets the {@link ChannelProcessor} instances to use in  {@link ChannelDecisionManagerImpl}
+     * @param channelProcessors
+     * @return
+     */
+    public ChannelSecurityConfigurer<H> channelProcessors(List<ChannelProcessor> channelProcessors) {
+        this.channelProcessors = channelProcessors;
+        return this;
+    }
+
+    private List<ChannelProcessor> getChannelProcessors(H http) {
+        if(channelProcessors != null) {
+            return channelProcessors;
+        }
+
+        InsecureChannelProcessor insecureChannelProcessor = new InsecureChannelProcessor();
+        SecureChannelProcessor secureChannelProcessor = new SecureChannelProcessor();
+
+        PortMapper portMapper = http.getSharedObject(PortMapper.class);
+        if(portMapper != null) {
+            RetryWithHttpEntryPoint httpEntryPoint = new RetryWithHttpEntryPoint();
+            httpEntryPoint.setPortMapper(portMapper);
+            insecureChannelProcessor.setEntryPoint(httpEntryPoint);
+
+            RetryWithHttpsEntryPoint httpsEntryPoint = new RetryWithHttpsEntryPoint();
+            httpsEntryPoint.setPortMapper(portMapper);
+            secureChannelProcessor.setEntryPoint(httpsEntryPoint);
+        }
+        insecureChannelProcessor = postProcess(insecureChannelProcessor);
+        secureChannelProcessor = postProcess(secureChannelProcessor);
+        return Arrays.<ChannelProcessor>asList(insecureChannelProcessor, secureChannelProcessor);
+    }
+
+
+    private ChannelSecurityConfigurer<H> addAttribute(String attribute, List<RequestMatcher> matchers) {
+        for(RequestMatcher matcher : matchers) {
+            Collection<ConfigAttribute> attrs = Arrays.<ConfigAttribute>asList(new SecurityConfig(attribute));
+            requestMap.put(matcher, attrs);
+        }
+        return this;
+    }
+
+    @Override
+    protected RequiresChannelUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
+        return new RequiresChannelUrl(requestMatchers);
+    }
+
+    public final class RequiresChannelUrl {
+        private List<RequestMatcher> requestMatchers;
+
+        private RequiresChannelUrl(List<RequestMatcher> requestMatchers) {
+            this.requestMatchers = requestMatchers;
+        }
+
+        public ChannelSecurityConfigurer<H> requiresSecure() {
+            return requires("REQUIRES_SECURE_CHANNEL");
+        }
+
+        public ChannelSecurityConfigurer<H> requiresInsecure() {
+            return requires("REQUIRES_INSECURE_CHANNEL");
+        }
+
+        public ChannelSecurityConfigurer<H> requires(String attribute) {
+            return addAttribute(attribute, requestMatchers);
+        }
+    }
+}

+ 82 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurer.java

@@ -0,0 +1,82 @@
+/*
+ * 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.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+
+/**
+ * Adds a Filter that will generate a login page if one is not specified otherwise when using {@link WebSecurityConfigurerAdapter}.
+ *
+ * <p>
+ * By default an {@link org.springframework.security.web.access.channel.InsecureChannelProcessor} and a {@link org.springframework.security.web.access.channel.SecureChannelProcessor} will be registered.
+ * </p>
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are conditionally populated
+ *
+ * <ul>
+ *     <li>{@link DefaultLoginPageViewFilter} if the {@link FormLoginConfigurer} did not have a login page specified</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared objects are created.
+ *isLogoutRequest
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ *     <li>{@link org.springframework.security.web.PortMapper} is used to create the default {@link org.springframework.security.web.access.channel.ChannelProcessor} instances</li>
+ *     <li>{@link FormLoginConfigurer} is used to determine if the {@link DefaultLoginPageConfigurer} should be added and how to configure it.</li>
+ * </ul>
+ *
+ * @see WebSecurityConfigurerAdapter
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>> extends
+        AbstractHttpConfigurer<H> {
+
+    private DefaultLoginPageViewFilter loginPageGeneratingFilter = new DefaultLoginPageViewFilter();
+
+    @Override
+    public void init(H http) throws Exception {
+        http.setSharedObject(DefaultLoginPageViewFilter.class, loginPageGeneratingFilter);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void configure(H http) throws Exception {
+        AuthenticationEntryPoint authenticationEntryPoint = null;
+        ExceptionHandlingConfigurer<?> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class);
+        if(exceptionConf != null) {
+            authenticationEntryPoint = exceptionConf.getAuthenticationEntryPoint();
+        }
+
+        if(loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
+            loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
+            http.addFilter(loginPageGeneratingFilter);
+        }
+    }
+
+
+}

+ 164 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java

@@ -0,0 +1,164 @@
+/*
+ * 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.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.access.AccessDeniedHandlerImpl;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
+import org.springframework.security.web.savedrequest.RequestCache;
+
+/**
+ * Adds exception handling for Spring Security related exceptions to an application. All properties have reasonable
+ * defaults, so no additional configuration is required other than applying this
+ * {@link org.springframework.security.config.annotation.SecurityConfigurer}.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ *     <li>{@link ExceptionTranslationFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared objects are created.
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ *     <li>{@link HttpSecurity#authenticationEntryPoint()} is used to process requests that require
+ *     authentication</li>
+ *     <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache} shared object is used to replay
+ *     the request after authentication is successful</li>
+ *     <li>{@link AuthenticationEntryPoint} - see {@link #authenticationEntryPoint(AuthenticationEntryPoint)} </li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+
+    private AuthenticationEntryPoint authenticationEntryPoint;
+
+    private AccessDeniedHandler accessDeniedHandler;
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#exceptionHandling()
+     */
+    public ExceptionHandlingConfigurer() {
+    }
+
+    /**
+     * Shortcut to specify the {@link AccessDeniedHandler} to be used is a specific error page
+     *
+     * @param accessDeniedUrl the URL to the access denied page (i.e. /errors/401)
+     * @return the {@link ExceptionHandlingConfigurer} for further customization
+     * @see AccessDeniedHandlerImpl
+     * @see {@link #accessDeniedHandler(org.springframework.security.web.access.AccessDeniedHandler)}
+     */
+    public ExceptionHandlingConfigurer<H> accessDeniedPage(String accessDeniedUrl) {
+        AccessDeniedHandlerImpl accessDeniedHandler = new AccessDeniedHandlerImpl();
+        accessDeniedHandler.setErrorPage(accessDeniedUrl);
+        return accessDeniedHandler(accessDeniedHandler);
+    }
+
+    /**
+     * Specifies the {@link AccessDeniedHandler} to be used
+     *
+     * @param accessDeniedHandler the {@link AccessDeniedHandler} to be used
+     * @return the {@link ExceptionHandlingConfigurer} for further customization
+     */
+    public ExceptionHandlingConfigurer<H> accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
+        this.accessDeniedHandler = accessDeniedHandler;
+        return this;
+    }
+
+    /**
+     * Sets the {@link AuthenticationEntryPoint} to be used. Defaults to the
+     * {@link HttpSecurity#getSharedObject(Class)} value. If that is not
+     * provided defaults to {@link Http403ForbiddenEntryPoint}.
+     *
+     * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use
+     * @return the {@link ExceptionHandlingConfigurer} for further customizations
+     */
+    public ExceptionHandlingConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
+        this.authenticationEntryPoint = authenticationEntryPoint;
+        return this;
+    }
+
+    /**
+     * Gets any explicitly configured {@link AuthenticationEntryPoint}
+     * @return
+     */
+    AuthenticationEntryPoint getAuthenticationEntryPoint() {
+        return this.authenticationEntryPoint;
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        AuthenticationEntryPoint entryPoint = getEntryPoint(http);
+        ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http));
+        if(accessDeniedHandler != null) {
+            exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler);
+        }
+        exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
+        http.addFilter(exceptionTranslationFilter);
+    }
+
+    /**
+     * Gets the {@link AuthenticationEntryPoint} according to the rules specified by {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
+     * @param http the {@link HttpSecurity} used to look up shared {@link AuthenticationEntryPoint}
+     * @return the {@link AuthenticationEntryPoint} to use
+     */
+    private AuthenticationEntryPoint getEntryPoint(H http) {
+        AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
+        if(entryPoint == null) {
+            AuthenticationEntryPoint sharedEntryPoint = http.getSharedObject(AuthenticationEntryPoint.class);
+            if(sharedEntryPoint != null) {
+                entryPoint = sharedEntryPoint;
+            } else {
+                entryPoint = new Http403ForbiddenEntryPoint();
+            }
+        }
+        return entryPoint;
+    }
+
+    /**
+     * Gets the {@link RequestCache} to use. If one is defined using
+     * {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an
+     * attempt to find a {@link RequestCache} shared object is made. If that fails, an {@link HttpSessionRequestCache}
+     * is used
+     *
+     * @param http the {@link HttpSecurity} to attempt to fined the shared object
+     * @return the {@link RequestCache} to use
+     */
+    private RequestCache getRequestCache(H http) {
+        RequestCache result = http.getSharedObject(RequestCache.class);
+        if(result != null) {
+            return result;
+        }
+        return new HttpSessionRequestCache();
+    }
+}

+ 296 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.java

@@ -0,0 +1,296 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.access.expression.SecurityExpressionHandler;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
+import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
+import org.springframework.security.web.access.expression.WebExpressionVoter;
+import org.springframework.security.web.util.RequestMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Adds URL based authorization based upon SpEL expressions to an application. At least one
+ * {@link org.springframework.web.bind.annotation.RequestMapping} needs to be mapped to {@link ConfigAttribute}'s for
+ * this {@link SecurityContextConfigurer} to have meaning.
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ *     <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * The following shared objects are populated to allow other {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to customize:
+ * <ul>
+ *     <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ *     <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
+ * </ul>
+ *
+ * @param <H> the type of {@link HttpSecurityBuilder} that is being configured
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see {@link org.springframework.security.config.annotation.web.builders.HttpSecurity#authorizeUrls()}
+ */
+public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractInterceptUrlConfigurer<H,ExpressionUrlAuthorizationConfigurer<H>,ExpressionUrlAuthorizationConfigurer<H>.AuthorizedUrl> {
+    static final String permitAll = "permitAll";
+    private static final String denyAll = "denyAll";
+    private static final String anonymous = "anonymous";
+    private static final String authenticated = "authenticated";
+    private static final String fullyAuthenticated = "fullyAuthenticated";
+    private static final String rememberMe = "rememberMe";
+
+    private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#authorizeUrls()
+     */
+    public ExpressionUrlAuthorizationConfigurer() {
+    }
+
+    /**
+     * Allows customization of the {@link SecurityExpressionHandler} to be used. The default is {@link DefaultWebSecurityExpressionHandler}
+     *
+     * @param expressionHandler the {@link SecurityExpressionHandler} to be used
+     * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization.
+     */
+    public ExpressionUrlAuthorizationConfigurer<H> expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
+        this.expressionHandler = expressionHandler;
+        return this;
+    }
+
+    @Override
+    protected final AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
+        return new AuthorizedUrl(requestMatchers);
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    final List<AccessDecisionVoter> getDecisionVoters() {
+        List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
+        WebExpressionVoter expressionVoter = new WebExpressionVoter();
+        expressionVoter.setExpressionHandler(expressionHandler);
+        decisionVoters.add(expressionVoter);
+        return decisionVoters;
+    }
+
+    @Override
+    final ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource() {
+        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = createRequestMap();
+        if(requestMap.isEmpty()) {
+            throw new IllegalStateException("At least one mapping is required (i.e. authorizeUrls().anyRequest.authenticated())");
+        }
+        return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, expressionHandler);
+    }
+
+    /**
+     * Allows registering multiple {@link RequestMatcher} instances to a collection of {@link ConfigAttribute} instances
+     *
+     * @param requestMatchers the {@link RequestMatcher} instances to register to the {@link ConfigAttribute} instances
+     * @param configAttributes the {@link ConfigAttribute} to be mapped by the {@link RequestMatcher} instances
+     * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization.
+     */
+    private ExpressionUrlAuthorizationConfigurer<H> interceptUrl(Iterable<? extends RequestMatcher> requestMatchers, Collection<ConfigAttribute> configAttributes) {
+        for(RequestMatcher requestMatcher : requestMatchers) {
+            addMapping(new UrlMapping(requestMatcher, configAttributes));
+        }
+        return this;
+    }
+
+    private static String hasRole(String role) {
+        Assert.notNull(role, "role cannot be null");
+        if (role.startsWith("ROLE_")) {
+            throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
+        }
+        return "hasRole('ROLE_" + role + "')";
+    }
+
+    private static String hasAuthority(String authority) {
+        return "hasAuthority('" + authority + "')";
+    }
+
+    private static String hasAnyAuthority(String... authorities) {
+        String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
+        return "hasAnyAuthority('" + anyAuthorities + "')";
+    }
+
+    private static String hasIpAddress(String ipAddressExpression) {
+        return "hasIpAddress('" + ipAddressExpression + "')";
+    }
+
+    public final class AuthorizedUrl {
+        private List<RequestMatcher> requestMatchers;
+        private boolean not;
+
+        /**
+         * Creates a new instance
+         *
+         * @param requestMatchers the {@link RequestMatcher} instances to map
+         */
+        private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
+            this.requestMatchers = requestMatchers;
+        }
+
+        /**
+         * Negates the following expression.
+         *
+         * @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not start with "ROLE_" as
+         *             this is automatically inserted.
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public AuthorizedUrl not() {
+            this.not = true;
+            return this;
+        }
+
+        /**
+         * Shortcut for specifying URLs require a particular role. If you do not want to have "ROLE_" automatically
+         * inserted see {@link #hasAuthority(String)}.
+         *
+         * @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not start with "ROLE_" as
+         *             this is automatically inserted.
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> hasRole(String role) {
+            return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
+        }
+
+        /**
+         * Specify that URLs require a particular authority.
+         *
+         * @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> hasAuthority(String authority) {
+            return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
+        }
+
+        /**
+         * Specify that URLs requires any of a number authorities.
+         *
+         * @param authorities the requests require at least one of the authorities (i.e. "ROLE_USER","ROLE_ADMIN" would
+         *                    mean either "ROLE_USER" or "ROLE_ADMIN" is required).
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> hasAnyAuthority(String... authorities) {
+            return access(ExpressionUrlAuthorizationConfigurer.hasAnyAuthority(authorities));
+        }
+
+        /**
+         * Specify that URLs requires a specific IP Address or
+         * <a href="http://forum.springsource.org/showthread.php?102783-How-to-use-hasIpAddress&p=343971#post343971">subnet</a>.
+         *
+         * @param ipaddressExpression the ipaddress (i.e. 192.168.1.79) or local subnet (i.e. 192.168.0/24)
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> hasIpAddress(String ipaddressExpression) {
+            return access(ExpressionUrlAuthorizationConfigurer.hasIpAddress(ipaddressExpression));
+        }
+
+        /**
+         * Specify that URLs are allowed by anyone.
+         *
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> permitAll() {
+            return access(permitAll);
+        }
+
+        /**
+         * Specify that URLs are allowed by anonymous users.
+         *
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> anonymous() {
+            return access(anonymous);
+        }
+
+        /**
+         * Specify that URLs are allowed by users that have been remembered.
+         *
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         * @see {@link RememberMeConfigurer}
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> rememberMe() {
+            return access(rememberMe);
+        }
+
+        /**
+         * Specify that URLs are not allowed by anyone.
+         *
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> denyAll() {
+            return access(denyAll);
+        }
+
+        /**
+         * Specify that URLs are allowed by any authenticated user.
+         *
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> authenticated() {
+            return access(authenticated);
+        }
+
+        /**
+         * Specify that URLs are allowed by users who have authenticated and were not "remembered".
+         *
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         * @see {@link RememberMeConfigurer}
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> fullyAuthenticated() {
+            return access(fullyAuthenticated);
+        }
+
+        /**
+         * Allows specifying that URLs are secured by an arbitrary expression
+         *
+         * @param attribute the expression to secure the URLs (i.e. "hasRole('ROLE_USER') and hasRole('ROLE_SUPER')")
+         * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
+         */
+        public ExpressionUrlAuthorizationConfigurer<H> access(String attribute) {
+            if(not) {
+                attribute = "!" + attribute;
+            }
+            interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
+            return ExpressionUrlAuthorizationConfigurer.this;
+        }
+    }
+}

+ 239 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java

@@ -0,0 +1,239 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.RememberMeServices;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+
+/**
+ * Adds form based authentication. All attributes have reasonable defaults
+ * making all parameters are optional. If no {@link #loginPage(String)} is
+ * specified, a default login page will be generated by the framework.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>
+ * {@link UsernamePasswordAuthenticationFilter}
+ * </li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * The following shared objects are populated
+ *
+ * <ul>
+ * <li> {@link AuthenticationEntryPoint} </li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>{@link HttpSecurity#getAuthenticationManager()}</li>
+ * <li>{@link RememberMeServices} - is optionally used. See {@link RememberMeConfigurer}</li>
+ * <li>{@link SessionAuthenticationStrategy} - is optionally used. See {@link SessionManagementConfigurer}</li>
+ * <li>{@link DefaultLoginPageViewFilter} - if present will be populated with information from the configuration</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H,FormLoginConfigurer<H>,UsernamePasswordAuthenticationFilter> {
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#formLogin()
+     */
+    public FormLoginConfigurer() {
+        super(createUsernamePasswordAuthenticationFilter(),"/login");
+        usernameParameter("username");
+        passwordParameter("password");
+    }
+
+    /**
+     * <p>
+     * Specifies the URL to send users to if login is required. If used with
+     * {@link WebSecurityConfigurerAdapter} a default login page will be
+     * generated when this attribute is not specified.
+     * </p>
+     *
+     * <p>
+     * If a URL is specified or this is not being used in conjuction with
+     * {@link WebSecurityConfigurerAdapter}, users are required to process the
+     * specified URL to generate a login page. In general, the login page should
+     * create a form that submits a request with the following requirements to
+     * work with {@link UsernamePasswordAuthenticationFilter}:
+     * </p>
+     *
+     * <ul>
+     * <li>It must be an HTTP POST</li>
+     * <li>It must be submitted to {@link #loginProcessingUrl(String)}</li>
+     * <li>It should include the username as an HTTP parameter by the name of
+     * {@link #usernameParameter(String)}</li>
+     * <li>It should include the password as an HTTP parameter by the name of
+     * {@link #passwordParameter(String)}</li>
+     * </ul>
+     *
+     * <h2>Example login.jsp</h2>
+     *
+     * Login pages can be rendered with any technology you choose so long as the
+     * rules above are followed. Below is an example login.jsp that can be used as
+     * a quick start when using JSP's or as a baseline to translate into another view
+     * technology.
+     *
+     * <pre>
+     * <!-- loginProcessingUrl should correspond to FormLoginConfigurer#loginProcessingUrl. Don't forget to perform a POST -->
+     * &lt;c:url value="/login" var="loginProcessingUrl"/&gt;
+     * &lt;form action="${loginProcessingUrl}" method="post"&gt;
+     *    &lt;fieldset&gt;
+     *        &lt;legend&gt;Please Login&lt;/legend&gt;
+     *        &lt;!-- use param.error assuming FormLoginConfigurer#failureUrl contains the query parameter error --&gt;
+     *        &lt;c:if test="${param.error != null}"&gt;
+     *            &lt;div&gt;
+     *                Failed to login.
+     *                &lt;c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}"&gt;
+     *                  Reason: &lt;c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" /&gt;
+     *                &lt;/c:if&gt;
+     *            &lt;/div&gt;
+     *        &lt;/c:if&gt;
+     *        &lt;!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout --&gt;
+     *        &lt;c:if test="${param.logout != null}"&gt;
+     *            &lt;div&gt;
+     *                You have been logged out.
+     *            &lt;/div&gt;
+     *        &lt;/c:if&gt;
+     *        &lt;p&gt;
+     *        &lt;label for="username"&gt;Username&lt;/label&gt;
+     *        &lt;input type="text" id="username" name="username"/&gt;
+     *        &lt;/p&gt;
+     *        &lt;p&gt;
+     *        &lt;label for="password"&gt;Password&lt;/label&gt;
+     *        &lt;input type="password" id="password" name="password"/&gt;
+     *        &lt;/p&gt;
+     *        &lt;!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter --&gt;
+     *        &lt;p&gt;
+     *        &lt;label for="remember-me"&gt;Remember Me?&lt;/label&gt;
+     *        &lt;input type="checkbox" id="remember-me" name="remember-me"/&gt;
+     *        &lt;/p&gt;
+     *        &lt;div&gt;
+     *            &lt;button type="submit" class="btn"&gt;Log in&lt;/button&gt;
+     *        &lt;/div&gt;
+     *    &lt;/fieldset&gt;
+     * &lt;/form&gt;
+     * </pre>
+     *
+     * @param loginPage
+     *            the login page to redirect to if authentication is required
+     *            (i.e. "/login")
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public FormLoginConfigurer<H> loginPage(String loginPage) {
+        return super.loginPage(loginPage);
+    }
+
+
+
+    /**
+     * The HTTP parameter to look for the username when performing
+     * authentication. Default is "username".
+     *
+     * @param usernameParameter
+     *            the HTTP parameter to look for the username when performing
+     *            authentication
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
+        getAuthenticationFilter().setUsernameParameter(usernameParameter);
+        return this;
+    }
+
+    /**
+     * The HTTP parameter to look for the password when performing
+     * authentication. Default is "password".
+     *
+     * @param passwordParameter
+     *            the HTTP parameter to look for the password when performing
+     *            authentication
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public FormLoginConfigurer<H> passwordParameter(String passwordParameter) {
+        getAuthenticationFilter().setPasswordParameter(passwordParameter);
+        return this;
+    }
+
+    @Override
+    public void init(H http) throws Exception {
+        super.init(http);
+        initDefaultLoginFilter(http);
+    }
+
+    /**
+     * Gets the HTTP parameter that is used to submit the username.
+     *
+     * @return the HTTP parameter that is used to submit the username
+     */
+    private String getUsernameParameter() {
+        return getAuthenticationFilter().getUsernameParameter();
+    }
+
+    /**
+     * Gets the HTTP parameter that is used to submit the password.
+     *
+     * @return the HTTP parameter that is used to submit the password
+     */
+    private String getPasswordParameter() {
+        return getAuthenticationFilter().getPasswordParameter();
+    }
+
+    /**
+     * If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
+     *
+     * @param http the {@link HttpSecurityBuilder} to use
+     */
+    private void initDefaultLoginFilter(H http) {
+        DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
+        if(loginPageGeneratingFilter != null && !isCustomLoginPage()) {
+            loginPageGeneratingFilter.setFormLoginEnabled(true);
+            loginPageGeneratingFilter.setUsernameParameter(getUsernameParameter());
+            loginPageGeneratingFilter.setPasswordParameter(getPasswordParameter());
+            loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
+            loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
+            loginPageGeneratingFilter.setAuthenticationUrl(getLoginProcessingUrl());
+        }
+    }
+
+    private static UsernamePasswordAuthenticationFilter createUsernamePasswordAuthenticationFilter() {
+        return new UsernamePasswordAuthenticationFilter() {
+            @Override
+            protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
+                return "POST".equals(request.getMethod()) && super.requiresAuthentication(request, response);
+            }
+        };
+    }
+}

+ 127 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java

@@ -0,0 +1,127 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+/**
+ * Adds HTTP basic based authentication. All attributes have reasonable defaults
+ * making all parameters are optional.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>
+ * {@link BasicAuthenticationFilter}
+ * </li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared objects are populated
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>{@link HttpSecurity#getAuthenticationManager()} </li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<B> {
+    private static final String DEFAULT_REALM = "Spring Security Application";
+
+    private AuthenticationEntryPoint authenticationEntryPoint;
+    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
+
+    /**
+     * Creates a new instance
+     * @throws Exception
+     * @see {@link HttpSecurity#httpBasic()}
+     */
+    public HttpBasicConfigurer() throws Exception {
+        realmName(DEFAULT_REALM);
+    }
+
+    /**
+     * Shortcut for {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
+     * specifying a {@link BasicAuthenticationEntryPoint} with the specified
+     * realm name.
+     *
+     * @param realmName
+     *            the HTTP Basic realm to use
+     * @return {@link HttpBasicConfigurer} for additional customization
+     * @throws Exception
+     */
+    public HttpBasicConfigurer<B> realmName(String realmName) throws Exception {
+        BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
+        basicAuthEntryPoint.setRealmName(realmName);
+        basicAuthEntryPoint.afterPropertiesSet();
+        return authenticationEntryPoint(basicAuthEntryPoint);
+    }
+
+    /**
+     * The {@link AuthenticationEntryPoint} to be po	pulated on
+     * {@link BasicAuthenticationFilter} in the event that authentication fails.
+     * The default to use {@link BasicAuthenticationEntryPoint} with the realm
+     * "Spring Security Application".
+     *
+     * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use
+     * @return {@link HttpBasicConfigurer} for additional customization
+     */
+    public HttpBasicConfigurer<B> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
+        this.authenticationEntryPoint = authenticationEntryPoint;
+        return this;
+    }
+
+    /**
+     * Specifies a custom {@link AuthenticationDetailsSource} to use for basic
+     * authentication. The default is {@link WebAuthenticationDetailsSource}.
+     *
+     * @param authenticationDetailsSource
+     *            the custom {@link AuthenticationDetailsSource} to use
+     * @return {@link HttpBasicConfigurer} for additional customization
+     */
+    public HttpBasicConfigurer<B> authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
+        this.authenticationDetailsSource = authenticationDetailsSource;
+        return this;
+    }
+
+    @Override
+    public void configure(B http) throws Exception {
+        AuthenticationManager authenticationManager = http.getAuthenticationManager();
+        BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(authenticationManager, authenticationEntryPoint);
+        if(authenticationDetailsSource != null) {
+            basicAuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
+        }
+        basicAuthenticationFilter = postProcess(basicAuthenticationFilter);
+        http.addFilter(basicAuthenticationFilter);
+    }
+}

+ 261 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurer.java

@@ -0,0 +1,261 @@
+/*
+ * 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 java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever;
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService;
+import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
+import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
+
+/**
+ * Adds support for J2EE pre authentication.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>
+ * {@link J2eePreAuthenticatedProcessingFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * <ul>
+ * <li>
+ * {@link AuthenticationEntryPoint}
+ * is populated with an {@link Http403ForbiddenEntryPoint}</li>
+ * <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
+ * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
+ * </li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>{@link HttpSecurity#getAuthenticationManager()}</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class JeeConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+    private J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter;
+    private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
+    private Set<String> mappableRoles = new HashSet<String>();
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#jee()
+     */
+    public JeeConfigurer() {
+    }
+
+    /**
+     * Specifies roles to use map from the {@link HttpServletRequest} to the
+     * {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)}
+     * returns true, the role is added to the {@link UserDetails}. This method
+     * is the equivalent of invoking {@link #mappableAuthorities(Set)}. Multiple
+     * invocations of {@link #mappableAuthorities(String...)} will override previous
+     * invocations.
+     *
+     * <p>
+     * There are no default roles that are mapped.
+     * </p>
+     *
+     * @param mappableRoles
+     *            the roles to attempt to map to the {@link UserDetails} (i.e.
+     *            "ROLE_USER", "ROLE_ADMIN", etc).
+     * @return the {@link JeeConfigurer} for further customizations
+     * @see SimpleMappableAttributesRetriever
+     * @see #mappableRoles(String...)
+     */
+    public JeeConfigurer<H> mappableAuthorities(String... mappableRoles) {
+        this.mappableRoles.clear();
+        for(String role : mappableRoles) {
+            this.mappableRoles.add(role);
+        }
+        return this;
+    }
+
+    /**
+     * Specifies roles to use map from the {@link HttpServletRequest} to the
+     * {@link UserDetails} and automatically prefixes it with "ROLE_". If
+     * {@link HttpServletRequest#isUserInRole(String)} returns true, the role is
+     * added to the {@link UserDetails}. This method is the equivalent of
+     * invoking {@link #mappableAuthorities(Set)}. Multiple invocations of
+     * {@link #mappableRoles(String...)} will override previous invocations.
+     *
+     * <p>
+     * There are no default roles that are mapped.
+     * </p>
+     *
+     * @param mappableRoles
+     *            the roles to attempt to map to the {@link UserDetails} (i.e.
+     *            "USER", "ADMIN", etc).
+     * @return the {@link JeeConfigurer} for further customizations
+     * @see SimpleMappableAttributesRetriever
+     * @see #mappableAuthorities(String...)
+     */
+    public JeeConfigurer<H> mappableRoles(String... mappableRoles) {
+        this.mappableRoles.clear();
+        for(String role : mappableRoles) {
+            this.mappableRoles.add("ROLE_" + role);
+        }
+        return this;
+    }
+
+    /**
+     * Specifies roles to use map from the {@link HttpServletRequest} to the
+     * {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)}
+     * returns true, the role is added to the {@link UserDetails}. This is the
+     * equivalent of {@link #mappableRoles(String...)}. Multiple invocations of
+     * {@link #mappableAuthorities(Set)} will override previous invocations.
+     *
+     * <p>
+     * There are no default roles that are mapped.
+     * </p>
+     *
+     * @param mappableRoles
+     *            the roles to attempt to map to the {@link UserDetails}.
+     * @return the {@link JeeConfigurer} for further customizations
+     * @see SimpleMappableAttributesRetriever
+     */
+    public JeeConfigurer<H> mappableAuthorities(Set<String> mappableRoles) {
+        this.mappableRoles = mappableRoles;
+        return this;
+    }
+
+    /**
+     * Specifies the {@link AuthenticationUserDetailsService} that is used with
+     * the {@link PreAuthenticatedAuthenticationProvider}. The default is a
+     * {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
+     *
+     * @param authenticatedUserDetailsService the {@link AuthenticationUserDetailsService} to use.
+     * @return the {@link JeeConfigurer} for further configuration
+     */
+    public JeeConfigurer<H> authenticatedUserDetailsService(
+            AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticatedUserDetailsService) {
+        this.authenticationUserDetailsService = authenticatedUserDetailsService;
+        return this;
+    }
+
+    /**
+     * Allows specifying the {@link J2eePreAuthenticatedProcessingFilter} to
+     * use. If {@link J2eePreAuthenticatedProcessingFilter} is provided, all of its attributes must also be
+     * configured manually (i.e. all attributes populated in the {@link JeeConfigurer} are not used).
+     *
+     * @param j2eePreAuthenticatedProcessingFilter the {@link J2eePreAuthenticatedProcessingFilter} to use.
+     * @return the {@link JeeConfigurer} for further configuration
+     */
+    public JeeConfigurer<H> j2eePreAuthenticatedProcessingFilter(
+            J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter) {
+        this.j2eePreAuthenticatedProcessingFilter = j2eePreAuthenticatedProcessingFilter;
+        return this;
+    }
+
+    /**
+     * Populates a {@link PreAuthenticatedAuthenticationProvider} into
+     * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
+     * and a {@link Http403ForbiddenEntryPoint} into
+     * {@link HttpSecurity#authenticationEntryPoint(org.springframework.security.web.AuthenticationEntryPoint)}
+     *
+     * @see org.springframework.security.config.annotation.SecurityConfigurerAdapter#init(org.springframework.security.config.annotation.SecurityBuilder)
+     */
+    @Override
+    public void init(H http) throws Exception {
+        PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
+        authenticationProvider.setPreAuthenticatedUserDetailsService(getUserDetailsService());
+        authenticationProvider = postProcess(authenticationProvider);
+
+        http
+            .authenticationProvider(authenticationProvider)
+            .setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        J2eePreAuthenticatedProcessingFilter filter = getFilter(http
+                .getAuthenticationManager());
+        http.addFilter(filter);
+    }
+
+    /**
+     * Gets the {@link J2eePreAuthenticatedProcessingFilter} or creates a default instance using the properties provided.
+     * @param authenticationManager the {@link AuthenticationManager} to use.
+     * @return the {@link J2eePreAuthenticatedProcessingFilter} to use.
+     */
+    private J2eePreAuthenticatedProcessingFilter getFilter(
+            AuthenticationManager authenticationManager) {
+        if (j2eePreAuthenticatedProcessingFilter == null) {
+            j2eePreAuthenticatedProcessingFilter = new J2eePreAuthenticatedProcessingFilter();
+            j2eePreAuthenticatedProcessingFilter
+                    .setAuthenticationManager(authenticationManager);
+            j2eePreAuthenticatedProcessingFilter
+                    .setAuthenticationDetailsSource(createWebAuthenticationDetailsSource());
+            j2eePreAuthenticatedProcessingFilter = postProcess(j2eePreAuthenticatedProcessingFilter);
+        }
+
+        return j2eePreAuthenticatedProcessingFilter;
+    }
+
+    /**
+     * Gets the {@link AuthenticationUserDetailsService} that was specified or
+     * defaults to {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
+     *
+     * @return the {@link AuthenticationUserDetailsService} to use
+     */
+    private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getUserDetailsService() {
+        return authenticationUserDetailsService == null ? new PreAuthenticatedGrantedAuthoritiesUserDetailsService()
+                : authenticationUserDetailsService;
+    }
+
+    /**
+     * Creates the
+     * {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource} to set on
+     * the {@link J2eePreAuthenticatedProcessingFilter}. It is populated with a
+     * {@link SimpleMappableAttributesRetriever}.
+     *
+     * @return the
+     *         {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource}
+     *         to use.
+     */
+    private J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource createWebAuthenticationDetailsSource() {
+        J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource detailsSource = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource();
+        SimpleMappableAttributesRetriever rolesRetriever = new SimpleMappableAttributesRetriever();
+        rolesRetriever.setMappableAttributes(mappableRoles);
+        detailsSource.setMappableRolesRetriever(rolesRetriever);
+
+        detailsSource = postProcess(detailsSource);
+        return detailsSource;
+    }
+}

+ 243 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java

@@ -0,0 +1,243 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpSession;
+
+import org.springframework.security.config.annotation.SecurityConfigurer;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
+import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+
+/**
+ * Adds logout support. Other {@link SecurityConfigurer} instances may invoke
+ * {@link #addLogoutHandler(LogoutHandler)} in the
+ * {@link #init(HttpSecurity)} phase.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>
+ * {@link LogoutFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared Objects are created
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * No shared objects are used.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see RememberMeConfigurer
+ */
+public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+    private List<LogoutHandler> logoutHandlers = new ArrayList<LogoutHandler>();
+    private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
+    private String logoutSuccessUrl = "/login?logout";
+    private LogoutSuccessHandler logoutSuccessHandler;
+    private String logoutUrl = "/logout";
+    private boolean permitAll;
+    private boolean customLogoutSuccess;
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#logout()
+     */
+    public LogoutConfigurer() {
+    }
+
+    /**
+     * Adds a {@link LogoutHandler}. The {@link SecurityContextLogoutHandler} is
+     * added as the last {@link LogoutHandler} by default.
+     *
+     * @param logoutHandler the {@link LogoutHandler} to add
+     * @return the {@link LogoutConfigurer} for further customization
+     */
+    public LogoutConfigurer<H> addLogoutHandler(LogoutHandler logoutHandler) {
+        this.logoutHandlers.add(logoutHandler);
+        return this;
+    }
+
+    /**
+     * Configures {@link SecurityContextLogoutHandler} to invalidate the {@link HttpSession} at the time of logout.
+     * @param invalidateHttpSession true if the {@link HttpSession} should be invalidated (default), or false otherwise.
+     * @return the {@link LogoutConfigurer} for further customization
+     */
+    public LogoutConfigurer<H> invalidateHttpSession(boolean invalidateHttpSession) {
+        contextLogoutHandler.setInvalidateHttpSession(invalidateHttpSession);
+        return this;
+    }
+
+    /**
+     * The URL that triggers logout to occur. The default is "/logout"
+     * @param logoutUrl the URL that will invoke logout.
+     * @return the {@link LogoutConfigurer} for further customization
+     */
+    public LogoutConfigurer<H> logoutUrl(String logoutUrl) {
+        this.logoutUrl = logoutUrl;
+        return this;
+    }
+
+    /**
+     * The URL to redirect to after logout has occurred. The default is
+     * "/login?logout". This is a shortcut for invoking
+     * {@link #logoutSuccessHandler(LogoutSuccessHandler)} with a
+     * {@link SimpleUrlLogoutSuccessHandler}.
+     *
+     * @param logoutSuccessUrl
+     *            the URL to redirect to after logout occurred
+     * @return the {@link LogoutConfigurer} for further customization
+     */
+    public LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) {
+        this.customLogoutSuccess = true;
+        this.logoutSuccessUrl = logoutSuccessUrl;
+        return this;
+    }
+
+    /**
+     * A shortcut for {@link #permitAll(boolean)} with <code>true</code> as an argument.
+     * @return the {@link LogoutConfigurer} for further customizations
+     */
+    public LogoutConfigurer<H> permitAll() {
+        return permitAll(true);
+    }
+
+    /**
+     * Allows specifying the names of cookies to be removed on logout success.
+     * This is a shortcut to easily invoke
+     * {@link #addLogoutHandler(LogoutHandler)} with a
+     * {@link CookieClearingLogoutHandler}.
+     *
+     * @param cookieNamesToClear the names of cookies to be removed on logout success.
+     * @return the {@link LogoutConfigurer} for further customization
+     */
+    public LogoutConfigurer<H> deleteCookies(String... cookieNamesToClear) {
+        return addLogoutHandler(new CookieClearingLogoutHandler(cookieNamesToClear));
+    }
+
+    /**
+     * Sets the {@link LogoutSuccessHandler} to use. If this is specified,
+     * {@link #logoutSuccessUrl(String)} is ignored.
+     *
+     * @param logoutSuccessHandler
+     *            the {@link LogoutSuccessHandler} to use after a user has been
+     *            logged out.
+     * @return the {@link LogoutConfigurer} for further customizations
+     */
+    public LogoutConfigurer<H> logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
+        this.logoutSuccessUrl = null;
+        this.customLogoutSuccess = true;
+        this.logoutSuccessHandler = logoutSuccessHandler;
+        return this;
+    }
+
+    /**
+     * Grants access to the {@link #logoutSuccessUrl(String)} and the {@link #logoutUrl(String)} for every user.
+     *
+     * @param permitAll if true grants access, else nothing is done
+     * @return the {@link LogoutConfigurer} for further customization.
+     */
+    public LogoutConfigurer<H> permitAll(boolean permitAll) {
+        this.permitAll = permitAll;
+        return this;
+    }
+
+    /**
+     * Gets the {@link LogoutSuccessHandler} if not null, otherwise creates a
+     * new {@link SimpleUrlLogoutSuccessHandler} using the
+     * {@link #logoutSuccessUrl(String)}.
+     *
+     * @return the {@link LogoutSuccessHandler} to use
+     */
+    private LogoutSuccessHandler getLogoutSuccessHandler() {
+        if(logoutSuccessHandler != null) {
+            return logoutSuccessHandler;
+        }
+        SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
+        logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
+        return logoutSuccessHandler;
+    }
+
+    @Override
+    public void init(H http) throws Exception {
+        if(permitAll) {
+            PermitAllSupport.permitAll(http, this.logoutUrl, this.logoutSuccessUrl);
+        }
+
+        DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
+        if(loginPageGeneratingFilter != null && !isCustomLogoutSuccess()) {
+            loginPageGeneratingFilter.setLogoutSuccessUrl(getLogoutSuccessUrl());
+        }
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        LogoutFilter logoutFilter = createLogoutFilter();
+        http.addFilter(logoutFilter);
+    }
+
+    /**
+     * Returns true if the logout success has been customized via
+     * {@link #logoutSuccessUrl(String)} or
+     * {@link #logoutSuccessHandler(LogoutSuccessHandler)}.
+     *
+     * @return true if logout success handling has been customized, else false
+     */
+    private boolean isCustomLogoutSuccess() {
+        return customLogoutSuccess;
+    }
+
+    /**
+     * Gets the logoutSuccesUrl or null if a
+     * {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured.
+     *
+     * @return the logoutSuccessUrl
+     */
+    private String getLogoutSuccessUrl() {
+        return logoutSuccessUrl;
+    }
+
+    /**
+     * Creates the {@link LogoutFilter} using the {@link LogoutHandler}
+     * instances, the {@link #logoutSuccessHandler(LogoutSuccessHandler)} and
+     * the {@link #logoutUrl(String)}.
+     *
+     * @return the {@link LogoutFilter} to use.
+     * @throws Exception
+     */
+    private LogoutFilter createLogoutFilter() throws Exception {
+        logoutHandlers.add(contextLogoutHandler);
+        LogoutHandler[] handlers = logoutHandlers.toArray(new LogoutHandler[logoutHandlers.size()]);
+        LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
+        result.setFilterProcessesUrl(logoutUrl);
+        result = postProcess(result);
+        return result;
+    }
+}

+ 73 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java

@@ -0,0 +1,73 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer.UrlMapping;
+import org.springframework.security.web.util.RequestMatcher;
+
+
+/**
+ * Configures non-null URL's to grant access to every URL
+ * @author Rob Winch
+ * @since 3.2
+ */
+final class PermitAllSupport {
+
+    @SuppressWarnings("unchecked")
+    public static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http, String... urls) {
+        ExpressionUrlAuthorizationConfigurer<?> configurer = http.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
+
+        if(configurer == null) {
+            throw new IllegalStateException("permitAll only works with HttpSecurity.authorizeUrls()");
+        }
+
+        for(String url : urls) {
+            if(url != null) {
+                configurer.addMapping(0, new UrlMapping(new ExactUrlRequestMatcher(url), SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
+            }
+        }
+    }
+
+    private final static class ExactUrlRequestMatcher implements RequestMatcher {
+        private String processUrl;
+
+        private ExactUrlRequestMatcher(String processUrl) {
+            this.processUrl = processUrl;
+        }
+
+        public boolean matches(HttpServletRequest request) {
+            String uri = request.getRequestURI();
+            String query = request.getQueryString();
+
+            if(query != null) {
+                uri += "?" + query;
+            }
+
+            if ("".equals(request.getContextPath())) {
+                return uri.equals(processUrl);
+            }
+
+            return uri.equals(request.getContextPath() + processUrl);
+        }
+    }
+
+    private PermitAllSupport() {}
+}

+ 115 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurer.java

@@ -0,0 +1,115 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.PortMapper;
+import org.springframework.security.web.PortMapperImpl;
+
+/**
+ * Allows configuring a shared {@link PortMapper} instance used to determine the
+ * ports when redirecting between HTTP and HTTPS. The {@link PortMapper} can be
+ * obtained from {@link HttpSecurity#getSharedObject(Class)}.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class PortMapperConfigurer<H extends HttpSecurityBuilder<H>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,H> {
+    private PortMapper portMapper;
+    private Map<String, String> httpsPortMappings = new HashMap<String,String>();
+
+    /**
+     * Creates a new instance
+     */
+    public PortMapperConfigurer() {
+    }
+
+    /**
+     * Allows specifying the {@link PortMapper} instance.
+     * @param portMapper
+     * @return
+     */
+    public PortMapperConfigurer<H> portMapper(PortMapper portMapper) {
+        this.portMapper = portMapper;
+        return this;
+    }
+
+    /**
+     * Adds a port mapping
+     * @param httpPort the HTTP port that maps to a specific HTTPS port.
+     * @return {@link HttpPortMapping} to define the HTTPS port
+     */
+    public HttpPortMapping http(int httpPort) {
+        return new HttpPortMapping(httpPort);
+    }
+
+    @Override
+    public void init(H http) throws Exception {
+        http.setSharedObject(PortMapper.class, getPortMapper());
+    }
+
+    /**
+     * Gets the {@link PortMapper} to use. If {@link #portMapper(PortMapper)}
+     * was not invoked, builds a {@link PortMapperImpl} using the port mappings
+     * specified with {@link #http(int)}.
+     *
+     * @return the {@link PortMapper} to use
+     */
+    private PortMapper getPortMapper() {
+        if(portMapper == null) {
+            PortMapperImpl portMapper = new PortMapperImpl();
+            portMapper.setPortMappings(httpsPortMappings);
+            this.portMapper = portMapper;
+        }
+        return portMapper;
+    }
+
+    /**
+     * Allows specifying the HTTPS port for a given HTTP port when redirecting
+     * between HTTP and HTTPS.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    public final class HttpPortMapping {
+        private final int httpPort;
+
+        /**
+         * Creates a new instance
+         * @param httpPort
+         * @see PortMapperConfigurer#http(int)
+         */
+        private HttpPortMapping(int httpPort) {
+            this.httpPort = httpPort;
+        }
+
+        /**
+         * Maps the given HTTP port to the provided HTTPS port and vice versa.
+         * @param httpsPort the HTTPS port to map to
+         * @return the {@link PortMapperConfigurer} for further customization
+         */
+        public PortMapperConfigurer<H> mapsTo(int httpsPort) {
+            httpsPortMappings.put(String.valueOf(httpPort), String.valueOf(httpsPort));
+            return PortMapperConfigurer.this;
+        }
+    }
+}

+ 352 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurer.java

@@ -0,0 +1,352 @@
+/*
+ * 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 java.util.UUID;
+
+import org.springframework.security.authentication.RememberMeAuthenticationProvider;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.RememberMeServices;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
+import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
+import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
+import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
+import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
+import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+
+/**
+ * Configures Remember Me authentication. This typically involves the user
+ * checking a box when they enter their username and password that states to
+ * "Remember Me".
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>
+ * {@link RememberMeAuthenticationFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * The following shared objects are populated
+ *
+ * <ul>
+ * <li>
+ * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
+ * is populated with a {@link RememberMeAuthenticationProvider}</li>
+ * <li>{@link RememberMeServices} is populated as a shared object and available on {@link HttpSecurity#getSharedObject(Class)}</li>
+ * <li>{@link LogoutConfigurer#addLogoutHandler(LogoutHandler)} is used to add a logout handler to clean up the remember me authentication.</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>{@link HttpSecurity#getAuthenticationManager()}</li>
+ * <li>{@link UserDetailsService} if no {@link #userDetailsService(UserDetailsService)} was specified.</li>
+ * <li> {@link DefaultLoginPageViewFilter} - if present will be populated with information from the configuration</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class RememberMeConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+    private AuthenticationSuccessHandler authenticationSuccessHandler;
+    private String key;
+    private RememberMeServices rememberMeServices;
+    private LogoutHandler logoutHandler;
+    private String rememberMeParameter = "remember-me";
+    private String rememberMeCookieName = "remember-me";
+    private PersistentTokenRepository tokenRepository;
+    private UserDetailsService userDetailsService;
+    private Integer tokenValiditySeconds;
+    private Boolean useSecureCookie;
+
+    /**
+     * Creates a new instance
+     */
+    public RememberMeConfigurer() {
+    }
+
+    /**
+     * Allows specifying how long (in seconds) a token is valid for
+     *
+     * @param tokenValiditySeconds
+     * @return {@link RememberMeConfigurer} for further customization
+     * @see AbstractRememberMeServices#setTokenValiditySeconds(int)
+     */
+    public RememberMeConfigurer<H> tokenValiditySeconds(int tokenValiditySeconds) {
+        this.tokenValiditySeconds = tokenValiditySeconds;
+        return this;
+    }
+
+    /**
+     *Whether the cookie should be flagged as secure or not. Secure cookies can only be sent over an HTTPS connection
+     * and thus cannot be accidentally submitted over HTTP where they could be intercepted.
+     * <p>
+     * By default the cookie will be secure if the request is secure. If you only want to use remember-me over
+     * HTTPS (recommended) you should set this property to {@code true}.
+     *
+     * @param useSecureCookie set to {@code true} to always user secure cookies, {@code false} to disable their use.
+     * @return the {@link RememberMeConfigurer} for further customization
+     * @see AbstractRememberMeServices#setUseSecureCookie(boolean)
+     */
+    public RememberMeConfigurer<H> useSecureCookie(boolean useSecureCookie) {
+        this.useSecureCookie = useSecureCookie;
+        return this;
+    }
+
+    /**
+     * Specifies the {@link UserDetailsService} used to look up the
+     * {@link UserDetails} when a remember me token is valid. The default is to
+     * use the {@link UserDetailsService} found by invoking
+     * {@link HttpSecurity#getSharedObject(Class)} which is set when using
+     * {@link WebSecurityConfigurerAdapter#registerAuthentication(org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder)}.
+     * Alternatively, one can populate {@link #rememberMeServices(RememberMeServices)}.
+     *
+     * @param userDetailsService
+     *            the {@link UserDetailsService} to configure
+     * @return the {@link RememberMeConfigurer} for further customization
+     * @see AbstractRememberMeServices
+     */
+    public RememberMeConfigurer<H> userDetailsService(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+        return this;
+    }
+
+    /**
+     * Specifies the {@link PersistentTokenRepository} to use. The default is to
+     * use {@link TokenBasedRememberMeServices} instead.
+     *
+     * @param tokenRepository
+     *            the {@link PersistentTokenRepository} to use
+     * @return the {@link RememberMeConfigurer} for further customization
+     */
+    public RememberMeConfigurer<H> tokenRepository(PersistentTokenRepository tokenRepository) {
+        this.tokenRepository = tokenRepository;
+        return this;
+    }
+
+    /**
+     * Sets the key to identify tokens created for remember me authentication. Default is a secure randomly generated
+     * key.
+     *
+     * @param key the key to identify tokens created for remember me authentication
+     * @return  the {@link RememberMeConfigurer} for further customization
+     */
+    public RememberMeConfigurer<H> key(String key) {
+        this.key = key;
+        return this;
+    }
+
+    /**
+     * Allows control over the destination a remembered user is sent to when they are successfully authenticated.
+     * By default, the filter will just allow the current request to proceed, but if an
+     * {@code AuthenticationSuccessHandler} is set, it will be invoked and the {@code doFilter()} method will return
+     * immediately, thus allowing the application to redirect the user to a specific URL, regardless of what the original
+     * request was for.
+     *
+     * @param authenticationSuccessHandler the strategy to invoke immediately before returning from {@code doFilter()}.
+     * @return {@link RememberMeConfigurer} for further customization
+     * @see RememberMeAuthenticationFilter#setAuthenticationSuccessHandler(AuthenticationSuccessHandler)
+     */
+    public RememberMeConfigurer<H> authenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
+        this.authenticationSuccessHandler = authenticationSuccessHandler;
+        return this;
+    }
+
+    /**
+     * Specify the {@link RememberMeServices} to use.
+     * @param rememberMeServices the {@link RememberMeServices} to use
+     * @return the {@link RememberMeConfigurer} for further customizations
+     * @see RememberMeServices
+     */
+    public RememberMeConfigurer<H> rememberMeServices(RememberMeServices rememberMeServices) {
+        this.rememberMeServices = rememberMeServices;
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void init(H http) throws Exception {
+        String key = getKey();
+        RememberMeServices rememberMeServices = getRememberMeServices(http, key);
+        http.setSharedObject(RememberMeServices.class, rememberMeServices);
+        LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
+        if(logoutConfigurer != null) {
+            logoutConfigurer.addLogoutHandler(logoutHandler);
+        }
+
+        RememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider(
+                key);
+        authenticationProvider = postProcess(authenticationProvider);
+        http.authenticationProvider(authenticationProvider);
+
+        initDefaultLoginFilter(http);
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter(
+                http.getAuthenticationManager(), rememberMeServices);
+        if (authenticationSuccessHandler != null) {
+            rememberMeFilter
+                    .setAuthenticationSuccessHandler(authenticationSuccessHandler);
+        }
+        rememberMeFilter = postProcess(rememberMeFilter);
+        http.addFilter(rememberMeFilter);
+    }
+
+    /**
+     * Returns the HTTP parameter used to indicate to remember the user at time of login.
+     * @return the HTTP parameter used to indicate to remember the user
+     */
+    private String getRememberMeParameter() {
+        return rememberMeParameter;
+    }
+
+    /**
+     * If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
+     *
+     * @param http the {@link HttpSecurityBuilder} to use
+     */
+    private void initDefaultLoginFilter(H http) {
+        DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
+        if(loginPageGeneratingFilter != null) {
+            loginPageGeneratingFilter.setRememberMeParameter(getRememberMeParameter());
+        }
+    }
+
+    /**
+     * Gets the {@link RememberMeServices} or creates the {@link RememberMeServices}.
+     * @param http the {@link HttpSecurity} to lookup shared objects
+     * @param key the {@link #key(String)}
+     * @return the {@link RememberMeServices} to use
+     * @throws Exception
+     */
+    private RememberMeServices getRememberMeServices(H http,
+            String key) throws Exception {
+        if (rememberMeServices != null) {
+            if (rememberMeServices instanceof LogoutHandler
+                    && logoutHandler == null) {
+                this.logoutHandler = (LogoutHandler) rememberMeServices;
+            }
+            return rememberMeServices;
+        }
+        AbstractRememberMeServices tokenRememberMeServices = createRememberMeServices(
+                http, key);
+        tokenRememberMeServices.setParameter(rememberMeParameter);
+        tokenRememberMeServices.setCookieName(rememberMeCookieName);
+        if (tokenValiditySeconds != null) {
+            tokenRememberMeServices
+                    .setTokenValiditySeconds(tokenValiditySeconds);
+        }
+        if (useSecureCookie != null) {
+            tokenRememberMeServices.setUseSecureCookie(useSecureCookie);
+        }
+        tokenRememberMeServices.afterPropertiesSet();
+        logoutHandler = tokenRememberMeServices;
+        rememberMeServices = tokenRememberMeServices;
+        return tokenRememberMeServices;
+    }
+
+    /**
+     * Creates the {@link RememberMeServices} to use when none is provided. The
+     * result is either {@link PersistentTokenRepository} (if a
+     * {@link PersistentTokenRepository} is specified, else
+     * {@link TokenBasedRememberMeServices}.
+     *
+     * @param http the {@link HttpSecurity} to lookup shared objects
+     * @param key the {@link #key(String)}
+     * @return the {@link RememberMeServices} to use
+     * @throws Exception
+     */
+    private AbstractRememberMeServices createRememberMeServices(
+            H http, String key) throws Exception {
+        return tokenRepository == null ? createTokenBasedRememberMeServices(
+                http, key) : createPersistentRememberMeServices(http, key);
+    }
+
+    /**
+     * Creates {@link TokenBasedRememberMeServices}
+     *
+     * @param http the {@link HttpSecurity} to lookup shared objects
+     * @param key the {@link #key(String)}
+     * @return the {@link TokenBasedRememberMeServices}
+     */
+    private AbstractRememberMeServices createTokenBasedRememberMeServices(
+            H http, String key) {
+        UserDetailsService userDetailsService = getUserDetailsService(http);
+        return new TokenBasedRememberMeServices(key, userDetailsService);
+    }
+
+    /**
+     * Creates {@link PersistentTokenBasedRememberMeServices}
+     *
+     * @param http the {@link HttpSecurity} to lookup shared objects
+     * @param key the {@link #key(String)}
+     * @return the {@link PersistentTokenBasedRememberMeServices}
+     */
+    private AbstractRememberMeServices createPersistentRememberMeServices(
+            H http, String key) {
+        UserDetailsService userDetailsService = getUserDetailsService(http);
+        return new PersistentTokenBasedRememberMeServices(key,
+                userDetailsService, tokenRepository);
+    }
+
+    /**
+     * Gets the {@link UserDetailsService} to use. Either the explicitly
+     * configure {@link UserDetailsService} from
+     * {@link #userDetailsService(UserDetailsService)} or a shared object from
+     * {@link HttpSecurity#getSharedObject(Class)}.
+     *
+     * @param http {@link HttpSecurity} to get the shared {@link UserDetailsService}
+     * @return the {@link UserDetailsService} to use
+     */
+    private UserDetailsService getUserDetailsService(H http) {
+        if(userDetailsService == null) {
+            userDetailsService = http.getSharedObject(UserDetailsService.class);
+        }
+        if(userDetailsService == null) {
+            throw new IllegalStateException("userDetailsService cannot be null. Invoke "
+                    + RememberMeConfigurer.class.getSimpleName() + "#userDetailsService(UserDetailsService) or see its javadoc for alternative approaches.");
+        }
+        return userDetailsService;
+    }
+
+    /**
+     * Gets the key to use for validating remember me tokens. Either the value
+     * passed into {@link #key(String)}, or a secure random string if none was
+     * specified.
+     *
+     * @return the remember me key to use
+     */
+    private String getKey() {
+        if (key == null) {
+            key = UUID.randomUUID().toString();
+        }
+        return key;
+    }
+}

+ 98 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java

@@ -0,0 +1,98 @@
+/*
+ * 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.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
+import org.springframework.security.web.savedrequest.RequestCache;
+import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
+
+/**
+ * Adds request cache for Spring Security. Specifically this ensures that
+ * requests that are saved (i.e. after authentication is required) are later
+ * replayed. All properties have reasonable defaults, so no additional
+ * configuration is required other than applying this
+ * {@link org.springframework.security.config.annotation.SecurityConfigurer}.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>{@link RequestCacheAwareFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared objects are created.
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache}
+ * shared object is used to replay the request after authentication is
+ * successful</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see RequestCache
+ */
+public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+
+    public RequestCacheConfigurer() {
+    }
+
+    /**
+     * Allows explicit configuration of the {@link RequestCache} to be used. Defaults to try finding a
+     * {@link RequestCache} as a shared object. Then falls back to a {@link HttpSessionRequestCache}.
+     *
+     * @param requestCache the explicit {@link RequestCache} to use
+     * @return the {@link RequestCacheConfigurer} for further customization
+     */
+    public RequestCacheConfigurer<H> requestCache(RequestCache requestCache) {
+        getBuilder().setSharedObject(RequestCache.class, requestCache);
+        return this;
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        RequestCache requestCache = getRequestCache(http);
+        RequestCacheAwareFilter requestCacheFilter = new RequestCacheAwareFilter(requestCache);
+        requestCacheFilter = postProcess(requestCacheFilter);
+        http.addFilter(requestCacheFilter);
+    }
+
+    /**
+     * Gets the {@link RequestCache} to use. If one is defined using
+     * {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an
+     * attempt to find a {@link RequestCache} shared object is made. If that fails, an {@link HttpSessionRequestCache}
+     * is used
+     *
+     * @param http the {@link HttpSecurity} to attempt to fined the shared object
+     * @return the {@link RequestCache} to use
+     */
+    private RequestCache getRequestCache(H http) {
+        RequestCache result = http.getSharedObject(RequestCache.class);
+        if(result != null) {
+            return result;
+        }
+        return new HttpSessionRequestCache();
+    }
+}

+ 95 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurer.java

@@ -0,0 +1,95 @@
+/*
+ * 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.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.context.SecurityContextPersistenceFilter;
+import org.springframework.security.web.context.SecurityContextRepository;
+
+/**
+ * Allows persisting and restoring of the {@link SecurityContext} found on the
+ * {@link SecurityContextHolder} for each request by configuring the
+ * {@link SecurityContextPersistenceFilter}. All properties have reasonable
+ * defaults, so no additional configuration is required other than applying this
+ * {@link org.springframework.security.config.annotation.SecurityConfigurer}.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>{@link SecurityContextPersistenceFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared objects are created.
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>If {@link SessionManagementConfigurer}, is provided and set to always,
+ * then the
+ * {@link SecurityContextPersistenceFilter#setForceEagerSessionCreation(boolean)}
+ * will be set to true.</li>
+ * <li>{@link SecurityContextRepository} must be set and is used on
+ * {@link SecurityContextPersistenceFilter}.</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#securityContext()
+     */
+    public SecurityContextConfigurer() {
+    }
+
+    /**
+     * Specifies the shared {@link SecurityContextRepository} that is to be used
+     * @param securityContextRepository the {@link SecurityContextRepository} to use
+     * @return the {@link HttpSecurity} for further customizations
+     */
+    public SecurityContextConfigurer<H> securityContextRepository(SecurityContextRepository securityContextRepository) {
+        getBuilder().setSharedObject(SecurityContextRepository.class, securityContextRepository);
+        return this;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void configure(H http) throws Exception {
+
+        SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
+        SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
+                securityContextRepository);
+        SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
+        SessionCreationPolicy sessionCreationPolicy = sessionManagement == null ? null
+                : sessionManagement.getSessionCreationPolicy();
+        if (SessionCreationPolicy.always == sessionCreationPolicy) {
+            securityContextFilter.setForceEagerSessionCreation(true);
+        }
+        securityContextFilter = postProcess(securityContextFilter);
+        http.addFilter(securityContextFilter);
+    }
+}

+ 75 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java

@@ -0,0 +1,75 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
+
+/**
+ * Implements select methods from the {@link HttpServletRequest} using the {@link SecurityContext} from the {@link SecurityContextHolder}.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>{@link SecurityContextHolderAwareRequestFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * No shared objects are created.
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * No shared Objects are used.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class ServletApiConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+    private SecurityContextHolderAwareRequestFilter securityContextRequestFilter = new SecurityContextHolderAwareRequestFilter();
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#servletApi()
+     */
+    public ServletApiConfigurer() {
+    }
+
+    public ServletApiConfigurer<H> rolePrefix(String rolePrefix) {
+        securityContextRequestFilter.setRolePrefix(rolePrefix);
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public H disable() {
+        getBuilder().removeConfigurer(getClass());
+        return getBuilder();
+    }
+
+    @Override
+    public void configure(H builder)
+            throws Exception {
+        securityContextRequestFilter = postProcess(securityContextRequestFilter);
+        builder.addFilter(securityContextRequestFilter);
+    }
+}

+ 39 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionCreationPolicy.java

@@ -0,0 +1,39 @@
+/*
+ * 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 javax.servlet.http.HttpSession;
+
+import org.springframework.security.core.context.SecurityContext;
+
+/**
+ * Specifies the various session creation policies for Spring Security.
+ *
+ * FIXME this should be removed once {@link org.springframework.security.config.http.SessionCreationPolicy} is made public.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public enum SessionCreationPolicy {
+    /** Always create an {@link HttpSession} */
+    always,
+    /** Spring Security will never create an {@link HttpSession}, but will use the {@link HttpSession} if it already exists */
+    never,
+    /** Spring Security will only create an {@link HttpSession} if required */
+    ifRequired,
+    /** Spring Security will never create an {@link HttpSession} and it will never use it to obtain the {@link SecurityContext} */
+    stateless
+}

+ 331 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java

@@ -0,0 +1,331 @@
+/*
+ * 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 javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.session.SessionRegistry;
+import org.springframework.security.core.session.SessionRegistryImpl;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
+import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
+import org.springframework.security.web.context.NullSecurityContextRepository;
+import org.springframework.security.web.context.SecurityContextRepository;
+import org.springframework.security.web.savedrequest.NullRequestCache;
+import org.springframework.security.web.savedrequest.RequestCache;
+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.Assert;
+
+/**
+ * Allows configuring session management.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>{@link SessionManagementFilter}</li>
+ * <li>{@link ConcurrentSessionFilter} if there are restrictions on how many concurrent sessions a user can have</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * The following shared objects are created:
+ *
+ * <ul>
+ * <li>{@link RequestCache}</li>
+ * <li>{@link SecurityContextRepository}</li>
+ * <li>{@link SessionManagementConfigurer}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * <ul>
+ * <li>{@link SecurityContextRepository}</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see SessionManagementFilter
+ * @see ConcurrentSessionFilter
+ */
+public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+    private SessionAuthenticationStrategy sessionAuthenticationStrategy = new SessionFixationProtectionStrategy();
+    private SessionRegistry sessionRegistry = new SessionRegistryImpl();
+    private Integer maximumSessions;
+    private String expiredUrl;
+    private boolean maxSessionsPreventsLogin;
+    private SessionCreationPolicy sessionPolicy = SessionCreationPolicy.ifRequired;
+    private boolean enableSessionUrlRewriting;
+    private String invalidSessionUrl;
+    private String sessionAuthenticationErrorUrl;
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#sessionManagement()
+     */
+    public SessionManagementConfigurer() {
+    }
+
+    /**
+     * Setting this attribute will inject the {@link SessionManagementFilter} with a
+     * {@link SimpleRedirectInvalidSessionStrategy} configured with the attribute value.
+     * When an invalid session ID is submitted, the strategy will be invoked,
+     * redirecting to the configured URL.
+     *
+     * @param invalidSessionUrl the URL to redirect to when an invalid session is detected
+     * @return the {@link SessionManagementConfigurer} for further customization
+     */
+    public SessionManagementConfigurer<H> invalidSessionUrl(String invalidSessionUrl) {
+        this.invalidSessionUrl = invalidSessionUrl;
+        return this;
+    }
+
+    /**
+     * Defines the URL of the error page which should be shown when the
+     * SessionAuthenticationStrategy raises an exception. If not set, an
+     * unauthorized (402) error code will be returned to the client. Note that
+     * this attribute doesn't apply if the error occurs during a form-based
+     * login, where the URL for authentication failure will take precedence.
+     *
+     * @param sessionAuthenticationErrorUrl
+     *            the URL to redirect to
+     * @return the {@link SessionManagementConfigurer} for further customization
+     */
+    public SessionManagementConfigurer<H> sessionAuthenticationErrorUrl(String sessionAuthenticationErrorUrl) {
+        this.sessionAuthenticationErrorUrl = sessionAuthenticationErrorUrl;
+        return this;
+    }
+
+    /**
+     * If set to true, allows HTTP sessions to be rewritten in the URLs when
+     * using {@link HttpServletResponse#encodeRedirectURL(String)} or
+     * {@link HttpServletResponse#encodeURL(String)}, otherwise disallows HTTP
+     * sessions to be included in the URL. This prevents leaking information to
+     * external domains.
+     *
+     * @param enableSessionUrlRewriting true if should allow the JSESSIONID to be rewritten into the URLs, else false (default)
+     * @return the {@link SessionManagementConfigurer} for further customization
+     * @see HttpSessionSecurityContextRepository#setDisableUrlRewriting(boolean)
+     */
+    public SessionManagementConfigurer<H> enableSessionUrlRewriting(boolean enableSessionUrlRewriting) {
+        this.enableSessionUrlRewriting = enableSessionUrlRewriting;
+        return this;
+    }
+
+    /**
+     * Allows specifying the {@link SessionCreationPolicy}
+     * @param sessionCreationPolicy the {@link SessionCreationPolicy} to use. Cannot be null.
+     * @return the {@link SessionManagementConfigurer} for further customizations
+     * @see SessionCreationPolicy
+     * @throws IllegalArgumentException if {@link SessionCreationPolicy} is null.
+     */
+    public SessionManagementConfigurer<H> sessionCreationPolicy(SessionCreationPolicy sessionCreationPolicy) {
+        Assert.notNull(sessionCreationPolicy, "sessionCreationPolicy cannot be null");
+        this.sessionPolicy = sessionCreationPolicy;
+        return this;
+    }
+
+    /**
+     * Allows explicitly specifying the {@link SessionAuthenticationStrategy}.
+     * The default is to use {@link SessionFixationProtectionStrategy}. If
+     * restricting the maximum number of sessions is configured,
+     * {@link ConcurrentSessionControlStrategy} will be used.
+     *
+     * @param sessionAuthenticationStrategy
+     * @return the {@link SessionManagementConfigurer} for further customizations
+     */
+    public SessionManagementConfigurer<H> sessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
+        this.sessionAuthenticationStrategy = sessionAuthenticationStrategy;
+        return this;
+    }
+
+    /**
+     * Controls the maximum number of sessions for a user. The default is to allow any number of users.
+     * @param maximumSessions the maximum number of sessions for a user
+     * @return the {@link SessionManagementConfigurer} for further customizations
+     */
+    public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
+        this.maximumSessions = maximumSessions;
+        this.sessionAuthenticationStrategy = null;
+        return new ConcurrencyControlConfigurer();
+    }
+
+    /**
+     * Allows configuring controlling of multiple sessions.
+     *
+     * @author Rob Winch
+     */
+    public final class ConcurrencyControlConfigurer {
+
+        /**
+         * The URL to redirect to if a user tries to access a resource and their
+         * session has been expired due to too many sessions for the current user.
+         * The default is to write a simple error message to the response.
+         *
+         * @param expiredUrl the URL to redirect to
+         * @return the {@link ConcurrencyControlConfigurer} for further customizations
+         */
+        public ConcurrencyControlConfigurer expiredUrl(String expiredUrl) {
+            SessionManagementConfigurer.this.expiredUrl = expiredUrl;
+            return this;
+        }
+
+        /**
+         * If true, prevents a user from authenticating when the
+         * {@link #maximumSessions(int)} has been reached. Otherwise (default), the user who
+         * authenticates is allowed access and an existing user's session is
+         * expired. The user's who's session is forcibly expired is sent to
+         * {@link #expiredUrl(String)}. The advantage of this approach is if a user
+         * accidentally does not log out, there is no need for an administrator to
+         * intervene or wait till their session expires.
+         *
+         * @param maxSessionsPreventsLogin true to have an error at time of authentication, else false (default)
+         * @return the {@link ConcurrencyControlConfigurer} for further customizations
+         */
+        public ConcurrencyControlConfigurer maxSessionsPreventsLogin(boolean maxSessionsPreventsLogin) {
+            SessionManagementConfigurer.this.maxSessionsPreventsLogin = maxSessionsPreventsLogin;
+            return this;
+        }
+
+        /**
+         * Controls the {@link SessionRegistry} implementation used. The default
+         * is {@link SessionRegistryImpl} which is an in memory implementation.
+         *
+         * @param sessionRegistry the {@link SessionRegistry} to use
+         * @return the {@link ConcurrencyControlConfigurer} for further customizations
+         */
+        public ConcurrencyControlConfigurer sessionRegistry(SessionRegistry sessionRegistry) {
+            SessionManagementConfigurer.this.sessionRegistry = sessionRegistry;
+            return this;
+        }
+
+        /**
+         * Used to chain back to the {@link SessionManagementConfigurer}
+         *
+         * @return the {@link SessionManagementConfigurer} for further customizations
+         */
+        public SessionManagementConfigurer<H> and() {
+            return SessionManagementConfigurer.this;
+        }
+
+        private ConcurrencyControlConfigurer() {}
+    }
+
+    @Override
+    public void init(H builder) throws Exception {
+        SecurityContextRepository securityContextRepository = builder.getSharedObject(SecurityContextRepository.class);
+        boolean stateless = isStateless();
+
+        if(securityContextRepository == null) {
+            if(stateless) {
+                builder.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository());
+            } else {
+                HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
+                httpSecurityRepository.setDisableUrlRewriting(!enableSessionUrlRewriting);
+                httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
+                builder.setSharedObject(SecurityContextRepository.class, httpSecurityRepository);
+            }
+        }
+
+        RequestCache requestCache = builder.getSharedObject(RequestCache.class);
+        if(requestCache == null) {
+            if(stateless) {
+                builder.setSharedObject(RequestCache.class, new NullRequestCache());
+            }
+        }
+        builder.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy());
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
+        SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy());
+        if(sessionAuthenticationErrorUrl != null) {
+            sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(sessionAuthenticationErrorUrl));
+        }
+        if(invalidSessionUrl != null) {
+            sessionManagementFilter.setInvalidSessionStrategy(new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl));
+        }
+        sessionManagementFilter = postProcess(sessionManagementFilter);
+
+        http.addFilter(sessionManagementFilter);
+        if(isConcurrentSessionControlEnabled()) {
+            ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry, expiredUrl);
+            concurrentSessionFilter = postProcess(concurrentSessionFilter);
+            http.addFilter(concurrentSessionFilter);
+        }
+    }
+
+    /**
+     * Gets the {@link SessionCreationPolicy}. Can not be null.
+     * @return the {@link SessionCreationPolicy}
+     */
+    SessionCreationPolicy getSessionCreationPolicy() {
+        return sessionPolicy;
+    }
+
+    /**
+     * Returns true if the {@link SessionCreationPolicy} allows session creation, else false
+     * @return true if the {@link SessionCreationPolicy} allows session creation
+     */
+    private boolean isAllowSessionCreation() {
+        return SessionCreationPolicy.always == sessionPolicy || SessionCreationPolicy.ifRequired == sessionPolicy;
+    }
+
+    /**
+     * Returns true if the {@link SessionCreationPolicy} is stateless
+     * @return
+     */
+    private boolean isStateless() {
+        return SessionCreationPolicy.stateless == sessionPolicy;
+    }
+
+    /**
+     * Gets the customized {@link SessionAuthenticationStrategy} if
+     * {@link #sessionAuthenticationStrategy(SessionAuthenticationStrategy)} was
+     * specified. Otherwise creates a default
+     * {@link SessionAuthenticationStrategy}.
+     *
+     * @return the {@link SessionAuthenticationStrategy} to use
+     */
+    private SessionAuthenticationStrategy getSessionAuthenticationStrategy() {
+        if(sessionAuthenticationStrategy != null) {
+            return sessionAuthenticationStrategy;
+        }
+        if(isConcurrentSessionControlEnabled()) {
+            ConcurrentSessionControlStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlStrategy(sessionRegistry);
+            concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
+            concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(maxSessionsPreventsLogin);
+            sessionAuthenticationStrategy = concurrentSessionControlStrategy;
+        }
+        return sessionAuthenticationStrategy;
+    }
+
+    /**
+     * Returns true if the number of concurrent sessions per user should be restricted.
+     * @return
+     */
+    private boolean isConcurrentSessionControlEnabled() {
+        return maximumSessions != null;
+    }
+}

+ 241 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java

@@ -0,0 +1,241 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.access.vote.AuthenticatedVoter;
+import org.springframework.security.access.vote.RoleVoter;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
+import org.springframework.security.web.util.RequestMatcher;
+import org.springframework.util.Assert;
+
+
+/**
+ * Adds URL based authorization using {@link DefaultFilterInvocationSecurityMetadataSource}. At least one
+ * {@link org.springframework.web.bind.annotation.RequestMapping} needs to be mapped to {@link ConfigAttribute}'s for
+ * this {@link SecurityContextConfigurer} to have meaning.
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ *     <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * The following shared objects are populated to allow other {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to customize:
+ * <ul>
+ *     <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ *     <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
+ * </ul>
+ *
+ * @param <H> the type of {@link HttpSecurityBuilder} that is being configured
+ * @param <C> the type of object that is being chained
+ *
+ * @author Rob Winch
+ * @since 3.2
+ * @see ExpressionUrlAuthorizationConfigurer
+ */
+public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>, C> extends AbstractInterceptUrlConfigurer<H,C,UrlAuthorizationConfigurer<H,C>.AuthorizedUrl> {
+
+    /**
+     * Creates the default {@link AccessDecisionVoter} instances used if an
+     * {@link AccessDecisionManager} was not specified using
+     * {@link #accessDecisionManager(AccessDecisionManager)}.
+     */
+    @Override
+    @SuppressWarnings("rawtypes")
+    final List<AccessDecisionVoter> getDecisionVoters() {
+        List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
+        decisionVoters.add(new RoleVoter());
+        decisionVoters.add(new AuthenticatedVoter());
+        return decisionVoters;
+    }
+
+    /**
+     * Creates the {@link FilterInvocationSecurityMetadataSource} to use. The
+     * implementation is a {@link DefaultFilterInvocationSecurityMetadataSource}
+     * .
+     */
+    @Override
+    FilterInvocationSecurityMetadataSource createMetadataSource() {
+        return new DefaultFilterInvocationSecurityMetadataSource(createRequestMap());
+    }
+
+    /**
+     * Chains the {@link RequestMatcher} creation to the {@link AuthorizedUrl} class.
+     */
+    @Override
+    protected AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
+        return new AuthorizedUrl(requestMatchers);
+    }
+
+    /**
+     * Adds a mapping of the {@link RequestMatcher} instances to the {@link ConfigAttribute} instances.
+     * @param requestMatchers the {@link RequestMatcher} instances that should map to the provided {@link ConfigAttribute} instances
+     * @param configAttributes the {@link ConfigAttribute} instances that should be mapped by the {@link RequestMatcher} instances
+     * @return the {@link UrlAuthorizationConfigurer} for further customizations
+     */
+    private UrlAuthorizationConfigurer<H,C> addMapping(Iterable<? extends RequestMatcher> requestMatchers, Collection<ConfigAttribute> configAttributes) {
+        for(RequestMatcher requestMatcher : requestMatchers) {
+            addMapping(new UrlMapping(requestMatcher, configAttributes));
+        }
+        return this;
+    }
+
+    /**
+     * Creates a String for specifying a user requires a role.
+     *
+     * @param role
+     *            the role that should be required which is prepended with ROLE_
+     *            automatically (i.e. USER, ADMIN, etc). It should not start
+     *            with ROLE_
+     * @return the {@link ConfigAttribute} expressed as a String
+     */
+    private static String hasRole(String role) {
+        Assert.isTrue(
+                !role.startsWith("ROLE_"),
+                role
+                        + " should not start with ROLE_ since ROLE_ is automatically prepended when using hasRole. Consider using hasAuthority or access instead.");
+        return "ROLE_" + role;
+    }
+
+    /**
+     * Creates a String for specifying that a user requires one of many roles.
+     *
+     * @param roles
+     *            the roles that the user should have at least one of (i.e.
+     *            ADMIN, USER, etc). Each role should not start with ROLE_ since
+     *            it is automatically prepended already.
+     * @return the {@link ConfigAttribute} expressed as a String
+     */
+    private static String[] hasAnyRole(String... roles) {
+        for(int i=0;i<roles.length;i++) {
+            roles[i] = "ROLE_" + roles[i];
+        }
+        return roles;
+    }
+
+    /**
+     * Creates a String for specifying that a user requires one of many authorities
+     * @param authorities the authorities that the user should have at least one of (i.e. ROLE_USER, ROLE_ADMIN, etc).
+     * @return the {@link ConfigAttribute} expressed as a String.
+     */
+    private static String[] hasAnyAuthority(String... authorities) {
+        return authorities;
+    }
+
+    /**
+     * Maps the specified {@link RequestMatcher} instances to {@link ConfigAttribute} instances.
+     *
+     * @author Rob Winch
+     * @since 3.2
+     */
+    public final class AuthorizedUrl {
+        private final List<RequestMatcher> requestMatchers;
+
+        /**
+         * Creates a new instance
+         * @param requestMatchers the {@link RequestMatcher} instances to map to some {@link ConfigAttribute} instances.
+         * @see UrlAuthorizationConfigurer#chainRequestMatchers(List)
+         */
+        private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
+            Assert.notEmpty(requestMatchers, "requestMatchers must contain at least one value");
+            this.requestMatchers = requestMatchers;
+        }
+
+        /**
+         * Specifies a user requires a role.
+         *
+         * @param role
+         *            the role that should be required which is prepended with ROLE_
+         *            automatically (i.e. USER, ADMIN, etc). It should not start
+         *            with ROLE_
+         * the {@link UrlAuthorizationConfigurer} for further customization
+         */
+        public UrlAuthorizationConfigurer<H,C> hasRole(String role) {
+            return access(UrlAuthorizationConfigurer.hasRole(role));
+        }
+
+        /**
+         * Specifies that a user requires one of many roles.
+         *
+         * @param roles
+         *            the roles that the user should have at least one of (i.e.
+         *            ADMIN, USER, etc). Each role should not start with ROLE_ since
+         *            it is automatically prepended already.
+         * @return the {@link UrlAuthorizationConfigurer} for further customization
+         */
+        public UrlAuthorizationConfigurer<H,C> hasAnyRole(String... roles) {
+            return access(UrlAuthorizationConfigurer.hasAnyRole(roles));
+        }
+
+        /**
+         * Specifies a user requires an authority.
+         *
+         * @param authority
+         *            the authority that should be required
+         * @return the {@link UrlAuthorizationConfigurer} for further customization
+         */
+        public UrlAuthorizationConfigurer<H,C> hasAuthority(String authority) {
+            return access(authority);
+        }
+
+        /**
+         * Specifies that a user requires one of many authorities
+         * @param authorities the authorities that the user should have at least one of (i.e. ROLE_USER, ROLE_ADMIN, etc).
+         * @return the {@link UrlAuthorizationConfigurer} for further customization
+         */
+        public UrlAuthorizationConfigurer<H,C> hasAnyAuthority(String... authorities) {
+            return access(UrlAuthorizationConfigurer.hasAnyAuthority(authorities));
+        }
+
+        /**
+         * Specifies that an anonymous user is allowed access
+         * @return the {@link UrlAuthorizationConfigurer} for further customization
+         */
+        public UrlAuthorizationConfigurer<H,C> anonymous() {
+            return hasRole("ROLE_ANONYMOUS");
+        }
+
+        /**
+         * Specifies that the user must have the specified {@link ConfigAttribute}'s
+         * @param attributes the {@link ConfigAttribute}'s that restrict access to a URL
+         * @return the {@link UrlAuthorizationConfigurer} for further customization
+         */
+        public UrlAuthorizationConfigurer<H,C> access(String... attributes) {
+            addMapping(requestMatchers, SecurityConfig.createList(attributes));
+            return UrlAuthorizationConfigurer.this;
+        }
+    }
+}

+ 196 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java

@@ -0,0 +1,196 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
+import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
+import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
+import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
+
+/**
+ * Adds X509 based pre authentication to an application. Since validating the
+ * certificate happens when the client connects, the requesting and validation
+ * of the client certificate should be performed by the container. Spring Security
+ * will then use the certificate to look up the {@link Authentication} for the user.
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>{@link X509AuthenticationFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * The following shared objects are created
+ *
+ * <ul>
+ * <li>
+ * {@link AuthenticationEntryPoint}
+ * is populated with an {@link Http403ForbiddenEntryPoint}</li>
+ * <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
+ * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
+ * </li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>A {@link UserDetailsService} shared object is used if no {@link AuthenticationUserDetailsService} is specified</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class X509Configurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
+    private X509AuthenticationFilter x509AuthenticationFilter;
+    private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
+    private String subjectPrincipalRegex;
+    private AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource;
+
+    /**
+     * Creates a new instance
+     * @see HttpSecurity#x509()
+     */
+    public X509Configurer() {
+    }
+
+    /**
+     * Allows specifying the entire {@link X509AuthenticationFilter}. If this is
+     * specified, the properties on {@link X509Configurer} will not be
+     * populated on the {@link X509AuthenticationFilter}.
+     *
+     * @param x509AuthenticationFilter the {@link X509AuthenticationFilter} to use
+     * @return the {@link X509Configurer} for further customizations
+     */
+    public X509Configurer<H> x509AuthenticationFilter(
+            X509AuthenticationFilter x509AuthenticationFilter) {
+        this.x509AuthenticationFilter = x509AuthenticationFilter;
+        return this;
+    }
+
+    /**
+     * Specifies the {@link AuthenticationDetailsSource}
+     *
+     * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} to use
+     * @return the {@link X509Configurer} to use
+     */
+    public X509Configurer<H> authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource) {
+        this.authenticationDetailsSource = authenticationDetailsSource;
+        return this;
+    }
+
+    /**
+     * Shortcut for invoking {@link #authenticationUserDetailsService(AuthenticationUserDetailsService)} with a {@link UserDetailsByNameServiceWrapper}.
+     *
+     * @param userDetailsService the {@link UserDetailsService} to use
+     * @return the {@link X509Configurer} for further customizations
+     */
+    public X509Configurer<H> userDetailsService(
+            UserDetailsService userDetailsService) {
+        UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService = new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
+        authenticationUserDetailsService.setUserDetailsService(userDetailsService);
+        return authenticationUserDetailsService(authenticationUserDetailsService);
+    }
+
+    /**
+     * Specifies the {@link AuthenticationUserDetailsService} to use. If not
+     * specified, the shared {@link UserDetailsService} will be used to create a
+     * {@link UserDetailsByNameServiceWrapper}.
+     *
+     * @param authenticationUserDetailsService the {@link AuthenticationUserDetailsService} to use
+     * @return the {@link X509Configurer} for further customizations
+     */
+    public X509Configurer<H> authenticationUserDetailsService(
+            AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService) {
+        this.authenticationUserDetailsService = authenticationUserDetailsService;
+        return this;
+    }
+
+    /**
+     * Specifies the regex to extract the principal from the certificate. If not
+     * specified, the default expression from
+     * {@link SubjectDnX509PrincipalExtractor} is used.
+     *
+     * @param subjectPrincipalRegex
+     *            the regex to extract the user principal from the certificate
+     *            (i.e. "CN=(.*?)(?:,|$)").
+     * @return the {@link X509Configurer} for further customizations
+     */
+    public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
+        this.subjectPrincipalRegex = subjectPrincipalRegex;
+        return this;
+    }
+
+    @Override
+    public void init(H http) throws Exception {
+        PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
+        authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http));
+
+        http
+            .authenticationProvider(authenticationProvider)
+            .setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        X509AuthenticationFilter filter = getFilter(http.getAuthenticationManager());
+        http.addFilter(filter);
+    }
+
+    private X509AuthenticationFilter getFilter(
+            AuthenticationManager authenticationManager) {
+        if (x509AuthenticationFilter == null) {
+            x509AuthenticationFilter = new X509AuthenticationFilter();
+            x509AuthenticationFilter.setAuthenticationManager(authenticationManager);
+            if(subjectPrincipalRegex != null) {
+                SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
+                principalExtractor.setSubjectDnRegex(subjectPrincipalRegex);
+                x509AuthenticationFilter.setPrincipalExtractor(principalExtractor);
+            }
+            if(authenticationDetailsSource != null) {
+                x509AuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
+            }
+            x509AuthenticationFilter = postProcess(x509AuthenticationFilter);
+        }
+
+        return x509AuthenticationFilter;
+    }
+
+    private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(H http) {
+        if(authenticationUserDetailsService == null) {
+            userDetailsService(http.getSharedObject(UserDetailsService.class));
+        }
+        return authenticationUserDetailsService;
+    }
+
+}

+ 463 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java

@@ -0,0 +1,463 @@
+/*
+ * 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.openid;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.openid4java.consumer.ConsumerException;
+import org.openid4java.consumer.ConsumerManager;
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
+import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
+import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
+import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
+import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
+import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.openid.AxFetchListFactory;
+import org.springframework.security.openid.OpenID4JavaConsumer;
+import org.springframework.security.openid.OpenIDAttribute;
+import org.springframework.security.openid.OpenIDAuthenticationFilter;
+import org.springframework.security.openid.OpenIDAuthenticationProvider;
+import org.springframework.security.openid.OpenIDAuthenticationToken;
+import org.springframework.security.openid.OpenIDConsumer;
+import org.springframework.security.openid.RegexBasedAxFetchListFactory;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
+import org.springframework.security.web.authentication.RememberMeServices;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+
+/**
+ * Adds support for OpenID based authentication.
+ *
+ * <h2>Example Configuration</h2>
+ *
+ * <pre>
+ *
+ * &#064;Configuration
+ * &#064;EnableWebSecurity
+ * public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
+ *
+ * 	&#064;Override
+ * 	protected void configure(HttpSecurity http) {
+ * 		http
+ * 			.authorizeUrls()
+ * 				.antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
+ * 				.and()
+ * 			.openidLogin()
+ * 				.permitAll();
+ * 	}
+ *
+ * 	&#064;Override
+ * 	protected void registerAuthentication(
+ * 			AuthenticationManagerBuilder auth) throws Exception {
+ * 		auth
+ * 			.inMemoryAuthentication()
+ * 				.withUser(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;)
+ * 					.password(&quot;password&quot;)
+ * 					.roles(&quot;USER&quot;);
+ * 	}
+ * }
+ * </pre>
+ *
+ * <h2>Security Filters</h2>
+ *
+ * The following Filters are populated
+ *
+ * <ul>
+ * <li>
+ * {@link OpenIDAuthenticationFilter}</li>
+ * </ul>
+ *
+ * <h2>Shared Objects Created</h2>
+ *
+ * <ul>
+ * <li>
+ * {@link AuthenticationEntryPoint}
+ * is populated with a {@link LoginUrlAuthenticationEntryPoint}</li>
+ * <li>A {@link OpenIDAuthenticationProvider} is populated into
+ * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
+ * </li>
+ * </ul>
+ *
+ * <h2>Shared Objects Used</h2>
+ *
+ * The following shared objects are used:
+ *
+ * <ul>
+ * <li>{@link HttpSecurity#getAuthenticationManager()}</li>
+ * <li>{@link RememberMeServices} - is optionally used. See
+ * {@link RememberMeConfigurer}</li>
+ * <li>{@link SessionAuthenticationStrategy} - is optionally used. See
+ * {@link SessionManagementConfigurer}</li>
+ * </ul>
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class OpenIDLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H,OpenIDLoginConfigurer<H>,OpenIDAuthenticationFilter> {
+    private OpenIDConsumer openIDConsumer;
+    private ConsumerManager consumerManager;
+    private AuthenticationUserDetailsService<OpenIDAuthenticationToken> authenticationUserDetailsService;
+    private List<AttributeExchangeConfigurer> attributeExchangeConfigurers = new ArrayList<AttributeExchangeConfigurer>();
+
+    /**
+     * Creates a new instance
+     */
+    public OpenIDLoginConfigurer() {
+        super(new OpenIDAuthenticationFilter(),"/login/openid");
+    }
+
+    /**
+     * Sets up OpenID attribute exchange for OpenID's matching the specified
+     * pattern.
+     *
+     * @param identifierPattern
+     *            the regular expression for matching on OpenID's (i.e.
+     *            "https://www.google.com/.*", ".*yahoo.com.*", etc)
+     * @return a {@link AttributeExchangeConfigurer} for further customizations of the attribute exchange
+     */
+    public AttributeExchangeConfigurer attributeExchange(String identifierPattern) {
+        AttributeExchangeConfigurer attributeExchangeConfigurer = new AttributeExchangeConfigurer(identifierPattern);
+        this.attributeExchangeConfigurers .add(attributeExchangeConfigurer);
+        return attributeExchangeConfigurer;
+    }
+
+    /**
+     * Allows specifying the {@link OpenIDConsumer} to be used. The default is
+     * using an {@link OpenID4JavaConsumer}.
+     *
+     * @param consumer
+     *            the {@link OpenIDConsumer} to be used
+     * @return the {@link OpenIDLoginConfigurer} for further customizations
+     */
+    public OpenIDLoginConfigurer<H> consumer(OpenIDConsumer consumer) {
+        this.openIDConsumer = consumer;
+        return this;
+    }
+
+    /**
+     * Allows specifying the {@link ConsumerManager} to be used. If specified,
+     * will be populated into an {@link OpenID4JavaConsumer}.
+     *
+     * <p>
+     * This is a shortcut for specifying the {@link OpenID4JavaConsumer} with a
+     * specific {@link ConsumerManager} on {@link #consumer(OpenIDConsumer)}.
+     * </p>
+     *
+     * @param consumerManager the {@link ConsumerManager} to use. Cannot be null.
+     * @return the {@link OpenIDLoginConfigurer} for further customizations
+     */
+    public OpenIDLoginConfigurer<H> consumerManager(ConsumerManager consumerManager) {
+        this.consumerManager = consumerManager;
+        return this;
+    }
+
+    /**
+     * The {@link AuthenticationUserDetailsService} to use. By default a
+     * {@link UserDetailsByNameServiceWrapper} is used with the
+     * {@link UserDetailsService} shared object found with
+     * {@link HttpSecurity#getSharedObject(Class)}.
+     *
+     * @param authenticationUserDetailsService the {@link AuthenticationDetailsSource} to use
+     * @return the {@link OpenIDLoginConfigurer} for further customizations
+     */
+    public OpenIDLoginConfigurer<H> authenticationUserDetailsService(AuthenticationUserDetailsService<OpenIDAuthenticationToken> authenticationUserDetailsService) {
+        this.authenticationUserDetailsService = authenticationUserDetailsService;
+        return this;
+    }
+
+    /**
+     * Specifies the URL used to authenticate OpenID requests. If the {@link HttpServletRequest}
+     * matches this URL the {@link OpenIDAuthenticationFilter} will attempt to
+     * authenticate the request. The default is "/login/openid".
+     *
+     * @param loginUrl
+     *            the URL used to perform authentication
+     * @return the {@link OpenIDLoginConfigurer} for additional customization
+     */
+    public OpenIDLoginConfigurer<H> loginProcessingUrl(String loginProcessingUrl) {
+        return super.loginProcessingUrl(loginProcessingUrl);
+    }
+
+    /**
+     * <p>
+     * Specifies the URL to send users to if login is required. If used with
+     * {@link WebSecurityConfigurerAdapter} a default login page will be
+     * generated when this attribute is not specified.
+     * </p>
+     *
+     * <p>
+     * If a URL is specified or this is not being used in conjuction with
+     * {@link WebSecurityConfigurerAdapter}, users are required to process the
+     * specified URL to generate a login page.
+     * </p>
+     *
+     * <ul>
+     * <li>It must be an HTTP POST</li>
+     * <li>It must be submitted to {@link #loginProcessingUrl(String)}</li>
+     * <li>It should include the OpenID as an HTTP parameter by the name of
+     * {@link OpenIDAuthenticationFilter#DEFAULT_CLAIMED_IDENTITY_FIELD}</li>
+     * </ul>
+     *
+     * @param loginPage the login page to redirect to if authentication is required (i.e. "/login")
+     * @return the {@link FormLoginConfigurer} for additional customization
+     */
+    public OpenIDLoginConfigurer<H> loginPage(String loginPage) {
+        return super.loginPage(loginPage);
+    }
+
+    @Override
+    public void init(H http) throws Exception {
+        super.init(http);
+
+        OpenIDAuthenticationProvider authenticationProvider = new OpenIDAuthenticationProvider();
+        authenticationProvider.setAuthenticationUserDetailsService(getAuthenticationUserDetailsService(http));
+        authenticationProvider = postProcess(authenticationProvider);
+        http.authenticationProvider(authenticationProvider);
+
+        initDefaultLoginFilter(http);
+    }
+
+    @Override
+    public void configure(H http) throws Exception {
+        getAuthenticationFilter().setConsumer(getConsumer());
+        super.configure(http);
+    }
+
+    /**
+     * Gets the {@link OpenIDConsumer} that was configured or defaults to an {@link OpenID4JavaConsumer}.
+     * @return the {@link OpenIDConsumer} to use
+     * @throws ConsumerException
+     */
+    private OpenIDConsumer getConsumer() throws ConsumerException {
+        if(openIDConsumer == null) {
+            openIDConsumer = new OpenID4JavaConsumer(getConsumerManager(), attributesToFetchFactory());
+        }
+        return openIDConsumer;
+    }
+
+    /**
+     * Gets the {@link ConsumerManager} that was configured or defaults to using a {@link ConsumerManager} with the default constructor.
+     * @return the {@link ConsumerManager} to use
+     */
+    private ConsumerManager getConsumerManager() {
+        if(this.consumerManager != null) {
+            return this.consumerManager;
+        }
+        return new ConsumerManager();
+    }
+
+    /**
+     * Creates an {@link RegexBasedAxFetchListFactory} using the attributes
+     * populated by {@link AttributeExchangeConfigurer}
+     *
+     * @return the {@link AxFetchListFactory} to use
+     */
+    private AxFetchListFactory attributesToFetchFactory() {
+        Map<String,List<OpenIDAttribute>> identityToAttrs = new HashMap<String,List<OpenIDAttribute>>();
+        for(AttributeExchangeConfigurer conf : attributeExchangeConfigurers) {
+            identityToAttrs.put(conf.identifier, conf.getAttributes());
+        }
+        return new RegexBasedAxFetchListFactory(identityToAttrs);
+    }
+
+    /**
+     * Gets the {@link AuthenticationUserDetailsService} that was configured or
+     * defaults to {@link UserDetailsByNameServiceWrapper} that uses a
+     * {@link UserDetailsService} looked up using
+     * {@link HttpSecurity#getSharedObject(Class)}
+     *
+     * @param http the current {@link HttpSecurity}
+     * @return the {@link AuthenticationUserDetailsService}.
+     */
+    private AuthenticationUserDetailsService<OpenIDAuthenticationToken> getAuthenticationUserDetailsService(
+            H http) {
+        if(authenticationUserDetailsService != null) {
+            return authenticationUserDetailsService;
+        }
+        return new UserDetailsByNameServiceWrapper<OpenIDAuthenticationToken>(http.getSharedObject(UserDetailsService.class));
+    }
+
+    /**
+     * If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
+     *
+     * @param http the {@link HttpSecurityBuilder} to use
+     */
+    private void initDefaultLoginFilter(H http) {
+        DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
+        if(loginPageGeneratingFilter != null && !isCustomLoginPage()) {
+            loginPageGeneratingFilter.setOpenIdEnabled(true);
+            loginPageGeneratingFilter.setOpenIDauthenticationUrl(getLoginProcessingUrl());
+            String loginPageUrl = loginPageGeneratingFilter.getLoginPageUrl();
+            if(loginPageUrl == null) {
+                loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
+                loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
+            }
+            loginPageGeneratingFilter.setOpenIDusernameParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD);
+        }
+    }
+
+
+    /**
+     * A class used to add OpenID attributes to look up
+     *
+     * @author Rob Winch
+     */
+    public final class AttributeExchangeConfigurer {
+        private final String identifier;
+        private List<OpenIDAttribute> attributes = new ArrayList<OpenIDAttribute>();
+        private List<AttributeConfigurer> attributeConfigurers = new ArrayList<AttributeConfigurer>();
+
+        /**
+         * Creates a new instance
+         * @param identifierPattern the pattern that attempts to match on the OpenID
+         * @see OpenIDLoginConfigurer#attributeExchange(String)
+         */
+        private AttributeExchangeConfigurer(String identifierPattern) {
+            this.identifier = identifierPattern;
+        }
+
+        /**
+         * Get the {@link OpenIDLoginConfigurer} to customize the OpenID configuration further
+         * @return the {@link OpenIDLoginConfigurer}
+         */
+        public OpenIDLoginConfigurer<H> and() {
+            return OpenIDLoginConfigurer.this;
+        }
+
+        /**
+         * Adds an {@link OpenIDAttribute} to be obtained for the configured OpenID pattern.
+         * @param attribute the {@link OpenIDAttribute} to obtain
+         * @return the {@link AttributeExchangeConfigurer} for further customization of attribute exchange
+         */
+        public AttributeExchangeConfigurer attribute(OpenIDAttribute attribute) {
+            this.attributes.add(attribute);
+            return this;
+        }
+
+        /**
+         * Adds an {@link OpenIDAttribute} with the given name
+         * @param name the name of the {@link OpenIDAttribute} to create
+         * @return an {@link AttributeConfigurer} to further configure the {@link OpenIDAttribute} that should be obtained.
+         */
+        public AttributeConfigurer attribute(String name) {
+            AttributeConfigurer attributeConfigurer = new AttributeConfigurer(name);
+            this.attributeConfigurers.add(attributeConfigurer);
+            return attributeConfigurer;
+        }
+
+        /**
+         * Gets the {@link OpenIDAttribute}'s for the configured OpenID pattern
+         * @return
+         */
+        private List<OpenIDAttribute> getAttributes() {
+            for(AttributeConfigurer config : attributeConfigurers) {
+                attributes.add(config.build());
+            }
+            attributeConfigurers.clear();
+            return attributes;
+        }
+
+        /**
+         * Configures an {@link OpenIDAttribute}
+         *
+         * @author Rob Winch
+         * @since 3.2
+         */
+        public final class AttributeConfigurer {
+            private String name;
+            private int count = 1;
+            private boolean required = false;
+            private String type;
+
+            /**
+             * Creates a new instance
+             * @param name the name of the attribute
+             * @see AttributeExchangeConfigurer#attribute(String)
+             */
+            private AttributeConfigurer(String name) {
+                this.name = name;
+            }
+
+            /**
+             * Specifies the number of attribute values to request. Default is 1.
+             * @param count the number of attributes to request.
+             * @return the {@link AttributeConfigurer} for further customization
+             */
+            public AttributeConfigurer count(int count) {
+                this.count = count;
+                return this;
+            }
+
+            /**
+             * Specifies that this attribute is required. The default is
+             * <code>false</code>. Note that as outlined in the OpenID
+             * specification, required attributes are not validated by the
+             * OpenID Provider. Developers should perform any validation in
+             * custom code.
+             *
+             * @param required specifies the attribute is required
+             * @return the {@link AttributeConfigurer} for further customization
+             */
+            public AttributeConfigurer required(boolean required) {
+                this.required = required;
+                return this;
+            }
+
+            /**
+             * The OpenID attribute type.
+             * @param type
+             * @return
+             */
+            public AttributeConfigurer type(String type) {
+                this.type = type;
+                return this;
+            }
+
+            /**
+             * Gets the {@link AttributeExchangeConfigurer} for further
+             * customization of the attributes
+             *
+             * @return the {@link AttributeConfigurer}
+             */
+            public AttributeExchangeConfigurer and() {
+                return AttributeExchangeConfigurer.this;
+            }
+
+            /**
+             * Builds the {@link OpenIDAttribute}.
+             * @return
+             */
+            private OpenIDAttribute build() {
+                OpenIDAttribute attribute = new OpenIDAttribute(name, type);
+                attribute.setCount(count);
+                attribute.setRequired(required);
+                return attribute;
+            }
+        }
+    }
+}

+ 25 - 0
config/src/test/groovy/org/springframework/security/config/annotation/AnyObjectPostProcessor.java

@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * Exists for mocking purposes to ensure that the Type information is found.
+ *
+ * @author Rob Winch
+ */
+public interface AnyObjectPostProcessor extends ObjectPostProcessor<Object> {
+
+}

+ 122 - 0
config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy

@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+import javax.servlet.Filter
+
+import org.springframework.beans.factory.NoSuchBeanDefinitionException
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import org.springframework.mock.web.MockFilterChain
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.AuthenticationProvider
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.core.context.SecurityContextImpl
+import org.springframework.security.web.FilterChainProxy
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
+import org.springframework.security.web.context.HttpRequestResponseHolder
+import org.springframework.security.web.context.HttpSessionSecurityContextRepository
+
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+/**
+ *
+ * @author Rob Winch
+ */
+abstract class BaseSpringSpec extends Specification {
+    @AutoCleanup
+    ConfigurableApplicationContext context
+
+    MockHttpServletRequest request
+    MockHttpServletResponse response
+    MockFilterChain chain
+
+    def setup() {
+        request = new MockHttpServletRequest(method:"GET")
+        response = new MockHttpServletResponse()
+        chain = new MockFilterChain()
+    }
+
+    AuthenticationManagerBuilder authenticationBldr = new AuthenticationManagerBuilder().inMemoryAuthentication().and()
+
+    def cleanup() {
+        SecurityContextHolder.clearContext()
+    }
+
+    def loadConfig(Class<?>... configs) {
+        context = new AnnotationConfigApplicationContext(configs)
+        context
+    }
+
+    def findFilter(Class<?> filter, int index = 0) {
+        filterChain(index).filters.find { filter.isAssignableFrom(it.class)}
+    }
+
+    def filterChain(int index=0) {
+        filterChains()[index]
+    }
+
+    def filterChains() {
+        context.getBean(FilterChainProxy).filterChains
+    }
+
+    Filter getSpringSecurityFilterChain() {
+        context.getBean("springSecurityFilterChain",Filter.class)
+    }
+
+    AuthenticationManager authenticationManager() {
+        context.getBean(AuthenticationManager)
+    }
+
+    AuthenticationManager getAuthenticationManager() {
+        try {
+            authenticationManager().delegateBuilder.getObject()
+        } catch(NoSuchBeanDefinitionException e) {}
+        findFilter(FilterSecurityInterceptor).authenticationManager
+    }
+
+    List<AuthenticationProvider> authenticationProviders() {
+        List<AuthenticationProvider> providers = new ArrayList<AuthenticationProvider>()
+        AuthenticationManager authenticationManager = getAuthenticationManager()
+        while(authenticationManager?.providers) {
+            providers.addAll(authenticationManager.providers)
+            authenticationManager = authenticationManager.parent
+        }
+        providers
+    }
+
+    AuthenticationProvider findAuthenticationProvider(Class<?> provider) {
+        authenticationProviders().find { provider.isAssignableFrom(it.class) }
+    }
+
+    def login(String username="user", String role="ROLE_USER") {
+        login(new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(role)))
+    }
+
+    def login(Authentication auth) {
+        HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository()
+        HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response)
+        repo.loadContext(requestResponseHolder)
+        repo.saveContext(new SecurityContextImpl(authentication:auth), requestResponseHolder.request, requestResponseHolder.response)
+    }
+}

+ 52 - 0
config/src/test/groovy/org/springframework/security/config/annotation/BaseWebSpecuritySpec.groovy

@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.web.FilterChainProxy;
+
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+/**
+ *
+ * @author Rob Winch
+ */
+abstract class BaseWebSpecuritySpec extends BaseSpringSpec {
+    FilterChainProxy springSecurityFilterChain
+    MockHttpServletRequest request
+    MockHttpServletResponse response
+    MockFilterChain chain
+
+    def setup() {
+        request = new MockHttpServletRequest()
+        response = new MockHttpServletResponse()
+        chain = new MockFilterChain()
+    }
+
+
+    def loadConfig(Class<?>... configs) {
+        super.loadConfig(configs)
+        springSecurityFilterChain = context.getBean(FilterChainProxy)
+    }
+
+
+}

+ 37 - 0
config/src/test/groovy/org/springframework/security/config/annotation/ConcereteSecurityConfigurerAdapter.java

@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Rob Winch
+ *
+ */
+class ConcereteSecurityConfigurerAdapter extends SecurityConfigurerAdapter<Object, SecurityBuilder<Object>> {
+    private List<Object> list = new ArrayList<Object>();
+
+    @Override
+    public void configure(SecurityBuilder<Object> builder) throws Exception {
+        list = postProcess(list);
+    }
+
+    public ConcereteSecurityConfigurerAdapter list(List<Object> l) {
+        this.list = l;
+        return this;
+    }
+}

+ 50 - 0
config/src/test/groovy/org/springframework/security/config/annotation/ObjectPostProcessorTests.java

@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * @author Rob Winch
+ *
+ */
+public class ObjectPostProcessorTests {
+
+    @Test
+    public void convertTypes() {
+        assertThat((Object)PerformConversion.perform(new ArrayList<Object>())).isInstanceOf(LinkedList.class);
+    }
+}
+
+class ListToLinkedListObjectPostProcessor implements ObjectPostProcessor<List<?>>{
+
+    @Override
+    public <O extends List<?>> O postProcess(O l) {
+        return (O) new LinkedList(l);
+    }
+}
+
+class PerformConversion {
+    public static List<?> perform(ArrayList<?> l) {
+        return new ListToLinkedListObjectPostProcessor().postProcess(l);
+    }
+}

+ 40 - 0
config/src/test/groovy/org/springframework/security/config/annotation/SecurityConfigurerAdapterTests.groovy

@@ -0,0 +1,40 @@
+/*
+ * 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
+
+import spock.lang.Specification
+
+/**
+ * @author Rob Winch
+ *
+ */
+class SecurityConfigurerAdapterTests extends Specification {
+    ConcereteSecurityConfigurerAdapter conf = new ConcereteSecurityConfigurerAdapter()
+
+    def "addPostProcessor closure"() {
+        setup:
+            SecurityBuilder<Object> builder = Mock()
+            conf.addObjectPostProcessor({ List l ->
+                l.add("a")
+                l
+            } as ObjectPostProcessor<List>)
+        when:
+            conf.init(builder)
+            conf.configure(builder)
+        then:
+            conf.list.contains("a")
+    }
+}

+ 90 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy

@@ -0,0 +1,90 @@
+/*
+ * 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.authentication
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationEventPublisher
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.AuthenticationProvider
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.ObjectPostProcessor
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.Authentication;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class AuthenticationManagerBuilderTests extends BaseSpringSpec {
+    def "add(AuthenticationProvider) does not perform registration"() {
+        setup:
+            ObjectPostProcessor opp = Mock()
+            AuthenticationProvider provider = Mock()
+            AuthenticationManagerBuilder builder = new AuthenticationManagerBuilder().objectPostProcessor(opp)
+        when: "Adding an AuthenticationProvider"
+            builder.authenticationProvider(provider)
+            builder.build()
+        then: "AuthenticationProvider is not passed into LifecycleManager (it should be managed externally)"
+            0 * opp._(_ as AuthenticationProvider)
+    }
+
+    // https://github.com/SpringSource/spring-security-javaconfig/issues/132
+    def "#132 Custom AuthenticationEventPublisher with Web registerAuthentication"() {
+        setup:
+            AuthenticationEventPublisher aep = Mock()
+        when:
+            AuthenticationManager am = new AuthenticationManagerBuilder()
+                .authenticationEventPublisher(aep)
+                .inMemoryAuthentication()
+                    .and()
+                .build()
+        then:
+            am.eventPublisher == aep
+    }
+
+    def "authentication-manager support multiple DaoAuthenticationProvider's"() {
+        setup:
+            loadConfig(MultiAuthenticationProvidersConfig)
+        when:
+            Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
+        then:
+            auth.name == "user"
+            auth.authorities*.authority == ['ROLE_USER']
+        when:
+            auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("admin","password"))
+        then:
+            auth.name == "admin"
+            auth.authorities*.authority.sort() == ['ROLE_ADMIN','ROLE_USER']
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class MultiAuthenticationProvidersConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER").and()
+                    .and()
+                .inMemoryAuthentication()
+                    .withUser("admin").password("password").roles("USER","ADMIN")
+        }
+    }
+}

+ 50 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/BaseAuthenticationConfig.groovy

@@ -0,0 +1,50 @@
+/*
+ * 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.authentication
+
+import java.rmi.registry.Registry;
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+
+/**
+ *
+ * @author Rob Winch
+ */
+@Configuration
+class BaseAuthenticationConfig {
+    protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+        auth
+            .inMemoryAuthentication()
+                .withUser("user").password("password").roles("USER").and()
+                .withUser("admin").password("password").roles("USER", "ADMIN").and()
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager() {
+        AuthenticationManagerBuilder registry = new AuthenticationManagerBuilder();
+        registerAuthentication(registry);
+        return registry.build();
+    }
+}

+ 92 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespaceAuthenticationManagerTests.groovy

@@ -0,0 +1,92 @@
+/*
+ * 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.authentication
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.Authentication
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class NamespaceAuthenticationManagerTests extends BaseSpringSpec {
+    def "authentication-manager@erase-credentials=true (default)"() {
+        when:
+            loadConfig(EraseCredentialsTrueDefaultConfig)
+            Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
+        then:
+            auth.principal.password == null
+            auth.credentials == null
+        when: "authenticate the same user"
+            auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
+        then: "successfully authenticate again"
+            noExceptionThrown()
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class EraseCredentialsTrueDefaultConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER")
+        }
+
+        // Only necessary to have access to verify the AuthenticationManager
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+    }
+
+    def "authentication-manager@erase-credentials=false"() {
+        when:
+            loadConfig(EraseCredentialsFalseConfig)
+            Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
+        then:
+            auth.credentials == "password"
+            auth.principal.password == "password"
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class EraseCredentialsFalseConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .eraseCredentials(false)
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER")
+        }
+
+        // Only necessary to have access to verify the AuthenticationManager
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+    }
+}

+ 83 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespaceAuthenticationProviderTests.groovy

@@ -0,0 +1,83 @@
+/*
+ * 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.authentication
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class NamespaceAuthenticationProviderTests extends BaseSpringSpec {
+    def "authentication-provider@ref"() {
+        when:
+            loadConfig(AuthenticationProviderRefConfig)
+        then:
+            authenticationProviders()[1] == AuthenticationProviderRefConfig.expected
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class AuthenticationProviderRefConfig extends WebSecurityConfigurerAdapter {
+        static DaoAuthenticationProvider expected = new DaoAuthenticationProvider()
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .authenticationProvider(expected)
+        }
+
+        // Only necessary to have access to verify the AuthenticationManager
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+    }
+
+    def "authentication-provider@user-service-ref"() {
+        when:
+            loadConfig(UserServiceRefConfig)
+        then:
+            findAuthenticationProvider(DaoAuthenticationProvider).userDetailsService == UserServiceRefConfig.expected
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class UserServiceRefConfig extends WebSecurityConfigurerAdapter {
+        static InMemoryUserDetailsManager expected = new InMemoryUserDetailsManager([] as Collection)
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .userDetailsService(expected)
+        }
+
+        // Only necessary to have access to verify the AuthenticationManager
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+    }
+}

+ 188 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespaceJdbcUserServiceTests.groovy

@@ -0,0 +1,188 @@
+/*
+ * 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.authentication
+
+import javax.sql.DataSource
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.NamespaceJdbcUserServiceTests.CustomJdbcUserServiceSampleConfig.CustomUserCache;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.userdetails.UserCache
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.JdbcUserDetailsManager
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class NamespaceJdbcUserServiceTests extends BaseSpringSpec {
+    def "jdbc-user-service"() {
+        when:
+            loadConfig(DataSourceConfig,JdbcUserServiceConfig)
+        then:
+            findAuthenticationProvider(DaoAuthenticationProvider).userDetailsService instanceof JdbcUserDetailsManager
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class JdbcUserServiceConfig extends WebSecurityConfigurerAdapter {
+        @Autowired
+        private DataSource dataSource;
+
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .jdbcAuthentication()
+                    .dataSource(dataSource) // jdbc-user-service@data-source-ref
+        }
+
+        // Only necessary to have access to verify the AuthenticationManager
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+    }
+
+    def "jdbc-user-service in memory testing sample"() {
+        when:
+            loadConfig(DataSourceConfig,JdbcUserServiceInMemorySampleConfig)
+        then:
+           Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
+            auth.authorities.collect {it.authority} == ['ROLE_USER']
+            auth.name == "user"
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class JdbcUserServiceInMemorySampleConfig extends WebSecurityConfigurerAdapter {
+        @Autowired
+        private DataSource dataSource;
+
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .jdbcAuthentication()
+                    .dataSource(dataSource)
+                    // imports the default schema (will fail if already exists)
+                    .withDefaultSchema()
+                    // adds this user automatically (will fail if already exists)
+                    .withUser("user")
+                        .password("password")
+                        .roles("USER")
+        }
+
+        // Only necessary to have access to verify the AuthenticationManager
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+    }
+
+    @Configuration
+    static class DataSourceConfig {
+        @Bean
+        public DataSource dataSource() {
+            EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
+            return builder.setType(EmbeddedDatabaseType.HSQL).build();
+        }
+    }
+
+    def "jdbc-user-service custom"() {
+        when:
+            loadConfig(CustomDataSourceConfig,CustomJdbcUserServiceSampleConfig)
+        then:
+            findAuthenticationProvider(DaoAuthenticationProvider).userDetailsService.userCache instanceof CustomUserCache
+        when:
+            Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
+        then:
+            auth.authorities.collect {it.authority}.sort() == ['ROLE_DBA','ROLE_USER']
+            auth.name == 'user'
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class CustomJdbcUserServiceSampleConfig extends WebSecurityConfigurerAdapter {
+        @Autowired
+        private DataSource dataSource;
+
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+            auth
+                .jdbcAuthentication()
+                    // jdbc-user-service@dataSource
+                    .dataSource(dataSource)
+                    // jdbc-user-service@cache-ref
+                    .userCache(new CustomUserCache())
+                    // jdbc-user-service@users-byusername-query
+                    .usersByUsernameQuery("select principal,credentials,true from users where principal = ?")
+                    // jdbc-user-service@authorities-by-username-query
+                    .authoritiesByUsernameQuery("select principal,role from roles where principal = ?")
+                    // jdbc-user-service@group-authorities-by-username-query
+                    .groupAuthoritiesByUsername(JdbcUserDetailsManager.DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY)
+                    // jdbc-user-service@role-prefix
+                    .rolePrefix("ROLE_")
+
+        }
+
+        // Only necessary to have access to verify the AuthenticationManager
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+
+        static class CustomUserCache implements UserCache {
+
+            @Override
+            public UserDetails getUserFromCache(String username) {
+                return null;
+            }
+
+            @Override
+            public void putUserInCache(UserDetails user) {
+            }
+
+            @Override
+            public void removeUserFromCache(String username) {
+            }
+        }
+    }
+
+    @Configuration
+    static class CustomDataSourceConfig {
+        @Bean
+        public DataSource dataSource() {
+            EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
+                // simulate that the DB already has the schema loaded and users in it
+                .addScript("CustomJdbcUserServiceSampleConfig.sql")
+            return builder.setType(EmbeddedDatabaseType.HSQL).build();
+        }
+    }
+}

+ 126 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/NamespacePasswordEncoderTests.groovy

@@ -0,0 +1,126 @@
+/*
+ * 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.authentication
+
+import static org.springframework.security.config.annotation.authentication.PasswordEncoderConfigurerConfigs.*
+
+import javax.sql.DataSource
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.authority.AuthorityUtils
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class NamespacePasswordEncoderTests extends BaseSpringSpec {
+    def "password-encoder@ref with in memory"() {
+        when:
+            loadConfig(PasswordEncoderWithInMemoryConfig)
+        then:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class PasswordEncoderWithInMemoryConfig extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+
+            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder()
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password(encoder.encode("password")).roles("USER").and()
+                    .passwordEncoder(encoder)
+        }
+    }
+
+    def "password-encoder@ref with jdbc"() {
+        when:
+            loadConfig(PasswordEncoderWithJdbcConfig)
+        then:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class PasswordEncoderWithJdbcConfig extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+
+            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder()
+            auth
+                .jdbcAuthentication()
+                    .withDefaultSchema()
+                    .dataSource(dataSource())
+                    .withUser("user").password(encoder.encode("password")).roles("USER").and()
+                    .passwordEncoder(encoder)
+        }
+
+        @Bean
+        public DataSource dataSource() {
+            EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
+            return builder.setType(EmbeddedDatabaseType.HSQL).build();
+        }
+    }
+
+    def "password-encoder@ref with userdetailsservice"() {
+        when:
+            loadConfig(PasswordEncoderWithUserDetailsServiceConfig)
+        then:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class PasswordEncoderWithUserDetailsServiceConfig extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+
+            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder()
+            User user = new User("user",encoder.encode("password"), AuthorityUtils.createAuthorityList("ROLE_USER"))
+            InMemoryUserDetailsManager uds = new InMemoryUserDetailsManager([user])
+            auth
+                .userDetailsService(uds)
+                    .passwordEncoder(encoder)
+        }
+
+        @Bean
+        public DataSource dataSource() {
+            EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
+            return builder.setType(EmbeddedDatabaseType.HSQL).build();
+        }
+    }
+}

+ 83 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/PasswordEncoderConfigurerConfigs.java

@@ -0,0 +1,83 @@
+/*
+ * 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.authentication;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+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.crypto.bcrypt.BCryptPasswordEncoder;
+
+/**
+ * Class Containing the {@link Configuration} for
+ * {@link PasswordEncoderConfigurerTests}. Separate to ensure the configuration
+ * compiles in Java (i.e. we are not using hidden methods).
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class PasswordEncoderConfigurerConfigs {
+
+    @EnableWebSecurity
+    @Configuration
+    static class PasswordEncoderConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+            BCryptPasswordEncoder encoder = passwordEncoder();
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password(encoder.encode("password")).roles("USER").and()
+                    .passwordEncoder(encoder);
+        }
+
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+        }
+
+        @Bean
+        public BCryptPasswordEncoder passwordEncoder() {
+            return new BCryptPasswordEncoder();
+        }
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class PasswordEncoderNoAuthManagerLoadsConfig extends WebSecurityConfigurerAdapter {
+        protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+            BCryptPasswordEncoder encoder = passwordEncoder();
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password(encoder.encode("password")).roles("USER").and()
+                    .passwordEncoder(encoder);
+        }
+
+        @Bean
+        public BCryptPasswordEncoder passwordEncoder() {
+            return new BCryptPasswordEncoder();
+        }
+    }
+}

+ 61 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/PasswordEncoderConfigurerTests.groovy

@@ -0,0 +1,61 @@
+/*
+ * 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.authentication
+
+import static org.springframework.security.config.annotation.authentication.PasswordEncoderConfigurerConfigs.*
+
+import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.ldap.core.ContextSource;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.AuthenticationProvider
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.SecurityBuilder;
+import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
+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.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class PasswordEncoderConfigurerTests extends BaseSpringSpec {
+    def "password-encoder@ref with No AuthenticationManager Bean"() {
+        when:
+            loadConfig(PasswordEncoderNoAuthManagerLoadsConfig)
+        then:
+            noExceptionThrown()
+    }
+
+    def "password-encoder@ref with AuthenticationManagerBuilder"() {
+        when:
+            loadConfig(PasswordEncoderConfig)
+            AuthenticationManager authMgr = authenticationManager()
+        then:
+            authMgr.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
+    }
+}

+ 120 - 0
config/src/test/groovy/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessorTests.groovy

@@ -0,0 +1,120 @@
+/*
+ * 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.configuration
+
+import javax.servlet.ServletConfig
+import javax.servlet.ServletContext
+
+import org.springframework.beans.factory.BeanClassLoaderAware
+import org.springframework.beans.factory.BeanFactoryAware
+import org.springframework.beans.factory.BeanNameAware
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.ApplicationContextAware
+import org.springframework.context.ApplicationEventPublisherAware
+import org.springframework.context.EnvironmentAware
+import org.springframework.context.MessageSourceAware
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockServletConfig
+import org.springframework.mock.web.MockServletContext
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor;
+import org.springframework.web.context.ServletConfigAware
+import org.springframework.web.context.ServletContextAware
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
+
+/**
+ *
+ * @author Rob Winch
+ */
+class AutowireBeanFactoryObjectPostProcessorTests extends BaseSpringSpec {
+
+    def "Verify All Aware methods are invoked"() {
+        setup:
+            ApplicationContextAware contextAware = Mock(ApplicationContextAware)
+            ApplicationEventPublisherAware publisher = Mock(ApplicationEventPublisherAware)
+            BeanClassLoaderAware classloader = Mock(BeanClassLoaderAware)
+            BeanFactoryAware beanFactory = Mock(BeanFactoryAware)
+            EnvironmentAware environment = Mock(EnvironmentAware)
+            MessageSourceAware messageSource = Mock(MessageSourceAware)
+            ServletConfigAware servletConfig = Mock(ServletConfigAware)
+            ServletContextAware servletContext = Mock(ServletContextAware)
+            DisposableBean disposable = Mock(DisposableBean)
+
+            context = new AnnotationConfigWebApplicationContext([servletConfig:new MockServletConfig(),servletContext:new MockServletContext()])
+            context.register(Config)
+            context.refresh()
+            context.start()
+
+            ObjectPostProcessor opp = context.getBean(ObjectPostProcessor)
+        when:
+            opp.postProcess(contextAware)
+        then:
+            1 * contextAware.setApplicationContext(!null)
+
+        when:
+            opp.postProcess(publisher)
+        then:
+            1 * publisher.setApplicationEventPublisher(!null)
+
+        when:
+            opp.postProcess(classloader)
+        then:
+            1 * classloader.setBeanClassLoader(!null)
+
+        when:
+            opp.postProcess(beanFactory)
+        then:
+            1 * beanFactory.setBeanFactory(!null)
+
+        when:
+            opp.postProcess(environment)
+        then:
+            1 * environment.setEnvironment(!null)
+
+        when:
+            opp.postProcess(messageSource)
+        then:
+            1 * messageSource.setMessageSource(!null)
+
+        when:
+            opp.postProcess(servletConfig)
+        then:
+            1 * servletConfig.setServletConfig(!null)
+
+        when:
+            opp.postProcess(servletContext)
+        then:
+            1 * servletContext.setServletContext(!null)
+
+        when:
+            opp.postProcess(disposable)
+            context.close()
+            context = null
+        then:
+            1 * disposable.destroy()
+    }
+
+    @Configuration
+    static class Config {
+        @Bean
+        public ObjectPostProcessor objectPostProcessor(AutowireCapableBeanFactory beanFactory) {
+            return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
+        }
+    }
+}

+ 85 - 0
config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfigurationTests.groovy

@@ -0,0 +1,85 @@
+/*
+ * 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.method.configuration
+
+import static org.fest.assertions.Assertions.assertThat
+import static org.junit.Assert.fail
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.springframework.context.ApplicationContext
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.AccessDecisionManager
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.access.ConfigAttribute
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.DefaultAuthenticationEventPublisher
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
+import org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapterTests.InMemoryAuthWithWebSecurityConfigurerAdapter
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.authority.AuthorityUtils
+
+/**
+ *
+ * @author Rob Winch
+ */
+public class GlobalMethodSecurityConfigurationTests extends BaseSpringSpec {
+    def "messages set when using GlobalMethodSecurityConfiguration"() {
+        when:
+            loadConfig(InMemoryAuthWithGlobalMethodSecurityConfig)
+        then:
+            authenticationManager.messages.messageSource instanceof ApplicationContext
+    }
+
+    def "AuthenticationEventPublisher is registered GlobalMethodSecurityConfiguration"() {
+        when:
+            loadConfig(InMemoryAuthWithGlobalMethodSecurityConfig)
+        then:
+            authenticationManager.eventPublisher instanceof DefaultAuthenticationEventPublisher
+        when:
+            Authentication auth = new UsernamePasswordAuthenticationToken("user",null,AuthorityUtils.createAuthorityList("ROLE_USER"))
+            authenticationManager.eventPublisher.publishAuthenticationSuccess(auth)
+        then:
+            InMemoryAuthWithGlobalMethodSecurityConfig.EVENT.authentication == auth
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(prePostEnabled = true)
+    public static class InMemoryAuthWithGlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration implements ApplicationListener<AuthenticationSuccessEvent> {
+        static AuthenticationSuccessEvent EVENT
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+        }
+
+        @Override
+        public void onApplicationEvent(AuthenticationSuccessEvent e) {
+            EVENT = e
+        }
+    }
+
+    AuthenticationManager getAuthenticationManager() {
+        context.getBean(MethodInterceptor).authenticationManager
+    }
+}

+ 51 - 0
config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.groovy

@@ -0,0 +1,51 @@
+/*
+ * 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.method.configuration;
+
+import javax.annotation.security.DenyAll
+
+import org.springframework.security.access.annotation.Secured
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.security.core.Authentication
+
+
+/**
+ *
+ * @author Rob Winch
+ */
+public interface MethodSecurityService {
+    @PreAuthorize("denyAll")
+    public String preAuthorize();
+
+    @Secured("ROLE_ADMIN")
+    public String secured();
+
+    @DenyAll
+    public String jsr250();
+
+    @Secured(["ROLE_USER","RUN_AS_SUPER"])
+    public Authentication runAs();
+
+    @PreAuthorize("permitAll")
+    public String preAuthorizePermitAll();
+
+    @PreAuthorize("hasPermission(#object,'read')")
+    public String hasPermission(String object);
+
+    @PostAuthorize("hasPermission(#object,'read')")
+    public String postHasPermission(String object);
+}

+ 62 - 0
config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.groovy

@@ -0,0 +1,62 @@
+/*
+ * 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.method.configuration;
+
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+public class MethodSecurityServiceImpl implements MethodSecurityService {
+
+    @Override
+    public String preAuthorize() {
+        return null;
+    }
+
+    @Override
+    public String secured() {
+        return null;
+    }
+
+    @Override
+    public String jsr250() {
+        return null;
+    }
+
+    @Override
+    public Authentication runAs() {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    @Override
+    public String preAuthorizePermitAll() {
+        return null;
+    }
+
+    @Override
+    public String hasPermission(String object) {
+        return null;
+    }
+
+    @Override
+    public String postHasPermission(String object) {
+        return null;
+    }
+}

+ 93 - 0
config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.groovy

@@ -0,0 +1,93 @@
+/*
+ * 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.method.configuration
+
+import static org.fest.assertions.Assertions.assertThat
+import static org.junit.Assert.fail
+
+import java.io.Serializable;
+
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.access.PermissionEvaluator;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.method.configuration.NamespaceGlobalMethodSecurityTests.BaseMethodConfig;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder
+
+/**
+ *
+ * @author Rob Winch
+ */
+public class NamespaceGlobalMethodSecurityExpressionHandlerTests extends BaseSpringSpec {
+    def setup() {
+        SecurityContextHolder.getContext().setAuthentication(
+                        new TestingAuthenticationToken("user", "password","ROLE_USER"))
+    }
+
+    def "global-method-security/expression-handler @PreAuthorize"() {
+        setup:
+        context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
+        MethodSecurityService service = context.getBean(MethodSecurityService)
+        when:
+        service.hasPermission("granted")
+        then:
+        noExceptionThrown()
+        when:
+        service.hasPermission("denied")
+        then:
+        thrown(AccessDeniedException)
+    }
+
+    def "global-method-security/expression-handler @PostAuthorize"() {
+        setup:
+        context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
+        MethodSecurityService service = context.getBean(MethodSecurityService)
+        when:
+        service.postHasPermission("granted")
+        then:
+        noExceptionThrown()
+        when:
+        service.postHasPermission("denied")
+        then:
+        thrown(AccessDeniedException)
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(prePostEnabled = true)
+    public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
+        @Override
+        protected MethodSecurityExpressionHandler expressionHandler() {
+            DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler()
+            expressionHandler.permissionEvaluator = new PermissionEvaluator() {
+                boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
+                    "granted" == targetDomainObject
+                }
+                boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
+                    throw new UnsupportedOperationException()
+                }
+            }
+            return expressionHandler
+        }
+    }
+}

+ 443 - 0
config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.groovy

@@ -0,0 +1,443 @@
+/*
+ * 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.method.configuration
+
+import static org.fest.assertions.Assertions.assertThat
+import static org.junit.Assert.fail
+
+import java.lang.reflect.Method
+
+import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
+import org.springframework.beans.factory.BeanCreationException
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.context.annotation.AdviceMode
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+import org.springframework.core.Ordered
+import org.springframework.security.access.AccessDecisionManager
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.access.ConfigAttribute
+import org.springframework.security.access.SecurityConfig
+import org.springframework.security.access.intercept.AfterInvocationManager
+import org.springframework.security.access.intercept.RunAsManager
+import org.springframework.security.access.intercept.RunAsManagerImpl
+import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor
+import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor
+import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource
+import org.springframework.security.access.method.MethodSecurityMetadataSource
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.BaseAuthenticationConfig;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+
+/**
+ *
+ * @author Rob Winch
+ */
+public class NamespaceGlobalMethodSecurityTests extends BaseSpringSpec {
+    def setup() {
+        SecurityContextHolder.getContext().setAuthentication(
+                        new TestingAuthenticationToken("user", "password","ROLE_USER"))
+    }
+
+    // --- access-decision-manager-ref ---
+
+    def "custom AccessDecisionManager can be used"() {
+        setup: "Create an instance with an AccessDecisionManager that always denies access"
+            context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        when:
+            service.preAuthorize()
+        then:
+            thrown(AccessDeniedException)
+        when:
+            service.secured()
+        then:
+            thrown(AccessDeniedException)
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+    public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
+        @Override
+        protected AccessDecisionManager accessDecisionManager() {
+            return new DenyAllAccessDecisionManager()
+        }
+
+        public static class DenyAllAccessDecisionManager implements AccessDecisionManager {
+            public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) {
+                throw new AccessDeniedException("Always Denied")
+            }
+            public boolean supports(ConfigAttribute attribute) {
+                return true
+            }
+            public boolean supports(Class<?> clazz) {
+                return true
+            }
+        }
+    }
+
+    // --- authentication-manager-ref ---
+
+    def "custom AuthenticationManager can be used"() {
+        when:
+            context = new AnnotationConfigApplicationContext(CustomAuthenticationConfig)
+        MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
+            interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
+        then:
+            thrown(UnsupportedOperationException)
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity
+    public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration {
+        @Override
+        protected AuthenticationManager authenticationManager() {
+            return new AuthenticationManager() {
+                Authentication authenticate(Authentication authentication) {
+                    throw new UnsupportedOperationException()
+                }
+            }
+        }
+    }
+
+    // --- jsr250-annotations ---
+
+    def "enable jsr250"() {
+        when:
+            context = new AnnotationConfigApplicationContext(Jsr250Config)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        then: "@Secured and @PreAuthorize are ignored"
+            service.secured() == null
+            service.preAuthorize() ==  null
+
+        when: "@DenyAll method invoked"
+            service.jsr250()
+        then: "access is denied"
+            thrown(AccessDeniedException)
+    }
+
+    @EnableGlobalMethodSecurity(jsr250Enabled = true)
+    @Configuration
+    public static class Jsr250Config extends BaseMethodConfig {
+    }
+
+    // --- metadata-source-ref ---
+
+    def "custom MethodSecurityMetadataSource can be used with higher priority than other sources"() {
+        setup:
+            context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomMethodSecurityMetadataSourceConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        when:
+            service.preAuthorize()
+        then:
+            thrown(AccessDeniedException)
+        when:
+            service.secured()
+        then:
+            thrown(AccessDeniedException)
+        when:
+            service.jsr250()
+        then:
+            thrown(AccessDeniedException)
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity
+    public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration {
+        @Override
+        protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
+            return new AbstractMethodSecurityMetadataSource() {
+                public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
+                    // require ROLE_NOBODY for any method on MethodSecurityService class
+                    return MethodSecurityService.isAssignableFrom(targetClass) ? [new SecurityConfig("ROLE_NOBODY")] : []
+                }
+                public Collection<ConfigAttribute> getAllConfigAttributes() {
+                    return null
+                }
+            }
+        }
+    }
+
+    // --- mode ---
+
+    def "aspectj mode works"() {
+        when:
+            context = new AnnotationConfigApplicationContext(AspectJModeConfig)
+        then:
+            AnnotationAwareAspectJAutoProxyCreator autoProxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator)
+            autoProxyCreator.proxyTargetClass == true
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, proxyTargetClass = true)
+    public static class AspectJModeConfig extends BaseMethodConfig {
+    }
+
+    def "aspectj mode works extending GlobalMethodSecurityConfiguration"() {
+        when:
+            context = new AnnotationConfigApplicationContext(BaseMethodConfig,AspectJModeExtendsGMSCConfig)
+        then:
+            AnnotationAwareAspectJAutoProxyCreator autoProxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator)
+            autoProxyCreator.proxyTargetClass == false
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ)
+    public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
+    }
+
+    // --- order ---
+
+    def order() {
+        when:
+            context = new AnnotationConfigApplicationContext(CustomOrderConfig)
+            MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
+        then:
+            advisor.order == 135
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(order = 135)
+    public static class CustomOrderConfig extends BaseMethodConfig {
+    }
+
+    def "order is defaulted to Ordered.LOWEST_PRECEDENCE when using @EnableGlobalMethodSecurity"() {
+        when:
+            context = new AnnotationConfigApplicationContext(DefaultOrderConfig)
+            MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
+        then:
+            advisor.order == Ordered.LOWEST_PRECEDENCE
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity
+    public static class DefaultOrderConfig extends BaseMethodConfig {
+    }
+
+    def "order is defaulted to Ordered.LOWEST_PRECEDENCE when extending GlobalMethodSecurityConfiguration"() {
+        when:
+            context = new AnnotationConfigApplicationContext(BaseMethodConfig,DefaultOrderExtendsMethodSecurityConfig)
+            MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
+        then:
+            advisor.order == Ordered.LOWEST_PRECEDENCE
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity
+    public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
+    }
+
+    // --- pre-post-annotations ---
+
+    def preAuthorize() {
+        when:
+            context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        then:
+            service.secured() == null
+            service.jsr250() == null
+
+        when:
+            service.preAuthorize()
+        then:
+            thrown(AccessDeniedException)
+    }
+
+    @EnableGlobalMethodSecurity(prePostEnabled = true)
+    @Configuration
+    public static class PreAuthorizeConfig extends BaseMethodConfig {
+    }
+
+    def "prePostEnabled extends GlobalMethodSecurityConfiguration"() {
+        when:
+            context = new AnnotationConfigApplicationContext(BaseMethodConfig,PreAuthorizeExtendsGMSCConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        then:
+            service.secured() == null
+            service.jsr250() == null
+
+        when:
+            service.preAuthorize()
+        then:
+            thrown(AccessDeniedException)
+    }
+
+    @EnableGlobalMethodSecurity(prePostEnabled = true)
+    @Configuration
+    public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
+    }
+
+    // --- proxy-target-class ---
+
+    def "proxying classes works"() {
+        when:
+            context = new AnnotationConfigApplicationContext(ProxyTargetClass)
+            MethodSecurityServiceImpl service = context.getBean(MethodSecurityServiceImpl)
+        then:
+            noExceptionThrown()
+    }
+
+    @EnableGlobalMethodSecurity(proxyTargetClass = true)
+    @Configuration
+    public static class ProxyTargetClass extends BaseMethodConfig {
+    }
+
+    def "proxying interfaces works"() {
+        when:
+            context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        then: "we get an instance of the interface"
+            noExceptionThrown()
+        when: "try to cast to the class"
+            MethodSecurityServiceImpl serviceImpl = service
+        then: "we get a class cast exception"
+            thrown(ClassCastException)
+    }
+
+    // --- run-as-manager-ref ---
+
+    def "custom RunAsManager"() {
+        when:
+            context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomRunAsManagerConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        then:
+            service.runAs().authorities.find { it.authority == "ROLE_RUN_AS_SUPER"}
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(securedEnabled = true)
+    public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration {
+        @Override
+        protected RunAsManager runAsManager() {
+            RunAsManagerImpl runAsManager = new RunAsManagerImpl()
+            runAsManager.setKey("some key")
+            return runAsManager
+        }
+    }
+
+    // --- secured-annotation ---
+
+    def "secured enabled"() {
+        setup:
+            context = new AnnotationConfigApplicationContext(SecuredConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        when:
+            service.secured()
+        then:
+            thrown(AccessDeniedException)
+            service.preAuthorize() == null
+            service.jsr250() == null
+    }
+
+    @EnableGlobalMethodSecurity(securedEnabled = true)
+    @Configuration
+    public static class SecuredConfig extends BaseMethodConfig {
+    }
+
+    // --- after-invocation-provider
+
+    def "custom AfterInvocationManager"() {
+        setup:
+            context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAfterInvocationManagerConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        when:
+            service.preAuthorizePermitAll()
+        then:
+            AccessDeniedException e = thrown()
+            e.message == "custom AfterInvocationManager"
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(prePostEnabled = true)
+    public static class CustomAfterInvocationManagerConfig extends GlobalMethodSecurityConfiguration {
+        @Override
+        protected AfterInvocationManager afterInvocationManager() {
+            return new AfterInvocationManagerStub()
+        }
+
+        public static class AfterInvocationManagerStub implements AfterInvocationManager {
+            Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,
+                Object returnedObject) throws AccessDeniedException {
+                throw new AccessDeniedException("custom AfterInvocationManager")
+            }
+
+            boolean supports(ConfigAttribute attribute) {
+                return true
+            }
+            boolean supports(Class<?> clazz) {
+                return true
+            }
+        }
+    }
+
+    // --- misc ---
+
+    def "good error message when no Enable annotation"() {
+        when:
+            context = new AnnotationConfigApplicationContext(ExtendsNoEnableAnntotationConfig)
+            MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
+            interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
+        then:
+            BeanCreationException e = thrown()
+            e.message.contains(EnableGlobalMethodSecurity.class.getName() + " is required")
+    }
+
+    @Configuration
+    public static class ExtendsNoEnableAnntotationConfig extends GlobalMethodSecurityConfiguration {
+        @Override
+        protected AuthenticationManager authenticationManager() {
+            return new AuthenticationManager() {
+                Authentication authenticate(Authentication authentication) {
+                    throw new UnsupportedOperationException()
+                }
+            }
+        }
+    }
+
+    def "import subclass of GlobalMethodSecurityConfiguration"() {
+        when:
+            context = new AnnotationConfigApplicationContext(ImportSubclassGMSCConfig)
+            MethodSecurityService service = context.getBean(MethodSecurityService)
+        then:
+            service.secured() == null
+            service.jsr250() == null
+
+        when:
+            service.preAuthorize()
+        then:
+            thrown(AccessDeniedException)
+    }
+
+    @Configuration
+    @Import(PreAuthorizeExtendsGMSCConfig)
+    public static class ImportSubclassGMSCConfig extends BaseMethodConfig {
+    }
+
+    @Configuration
+    public static class BaseMethodConfig extends BaseAuthenticationConfig {
+        @Bean
+        public MethodSecurityService methodSecurityService() {
+            return new MethodSecurityServiceImpl()
+        }
+    }
+}

+ 129 - 0
config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/SampleEnableGlobalMethodSecurityTests.groovy

@@ -0,0 +1,129 @@
+/*
+ * 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.method.configuration
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.access.PermissionEvaluator;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder
+
+/**
+ * Demonstrate the samples
+ *
+ * @author Rob Winch
+ *
+ */
+public class SampleEnableGlobalMethodSecurityTests extends BaseSpringSpec {
+    def setup() {
+        SecurityContextHolder.getContext().setAuthentication(
+                        new TestingAuthenticationToken("user", "password","ROLE_USER"))
+    }
+
+    def preAuthorize() {
+        when:
+        loadConfig(SampleWebSecurityConfig)
+        MethodSecurityService service = context.getBean(MethodSecurityService)
+        then:
+        service.secured() == null
+        service.jsr250() == null
+
+        when:
+        service.preAuthorize()
+        then:
+        thrown(AccessDeniedException)
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(prePostEnabled=true)
+    public static class SampleWebSecurityConfig {
+        @Bean
+        public MethodSecurityService methodSecurityService() {
+            return new MethodSecurityServiceImpl()
+        }
+
+        @Bean
+        public AuthenticationManager authenticationManager() throws Exception {
+            return new AuthenticationManagerBuilder()
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER").and()
+                    .withUser("admin").password("password").roles("USER", "ADMIN").and()
+                    .and()
+                .build();
+        }
+    }
+
+    def 'custom permission handler'() {
+        when:
+        loadConfig(CustomPermissionEvaluatorWebSecurityConfig)
+        MethodSecurityService service = context.getBean(MethodSecurityService)
+        then:
+        service.hasPermission("allowed") == null
+
+        when:
+        service.hasPermission("denied") == null
+        then:
+        thrown(AccessDeniedException)
+    }
+
+    @Configuration
+    @EnableGlobalMethodSecurity(prePostEnabled=true)
+    public static class CustomPermissionEvaluatorWebSecurityConfig extends GlobalMethodSecurityConfiguration {
+        @Bean
+        public MethodSecurityService methodSecurityService() {
+            return new MethodSecurityServiceImpl()
+        }
+
+        @Override
+        protected MethodSecurityExpressionHandler expressionHandler() {
+            DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+            expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
+            return expressionHandler;
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+            throws Exception {
+            auth
+                .inMemoryAuthentication()
+                .withUser("user").password("password").roles("USER").and()
+                .withUser("admin").password("password").roles("USER", "ADMIN");
+        }
+    }
+
+    static class CustomPermissionEvaluator implements PermissionEvaluator {
+        public boolean hasPermission(Authentication authentication,
+                Object targetDomainObject, Object permission) {
+            return !"denied".equals(targetDomainObject);
+        }
+
+        public boolean hasPermission(Authentication authentication,
+                Serializable targetId, String targetType, Object permission) {
+            return !"denied".equals(targetId);
+        }
+
+    }
+}

+ 97 - 0
config/src/test/groovy/org/springframework/security/config/annotation/provisioning/UserDetailsManagerConfigurerTests.groovy

@@ -0,0 +1,97 @@
+/*
+ * 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.provisioning
+
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer;
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+
+import spock.lang.Specification
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+class UserDetailsManagerConfigurerTests extends Specification {
+
+    def "all attributes supported"() {
+        setup:
+            InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
+        when:
+            UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
+                .withUser("user")
+                    .password("password")
+                    .roles("USER")
+                    .disabled(true)
+                    .accountExpired(true)
+                    .accountLocked(true)
+                    .credentialsExpired(true)
+                    .build()
+        then:
+            userDetails.username == 'user'
+            userDetails.password == 'password'
+            userDetails.authorities.collect { it.authority } == ["ROLE_USER"]
+            !userDetails.accountNonExpired
+            !userDetails.accountNonLocked
+            !userDetails.credentialsNonExpired
+            !userDetails.enabled
+    }
+
+    def "authorities(GrantedAuthorities...) works"() {
+        setup:
+            InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
+            SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER")
+        when:
+            UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
+                .withUser("user")
+                    .password("password")
+                    .authorities(authority)
+                    .build()
+        then:
+            userDetails.authorities == [authority] as Set
+    }
+
+    def "authorities(String...) works"() {
+        setup:
+            InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
+            String authority = "ROLE_USER"
+        when:
+            UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
+                .withUser("user")
+                    .password("password")
+                    .authorities(authority)
+                    .build()
+        then:
+            userDetails.authorities.collect { it.authority } == [authority]
+    }
+
+
+    def "authorities(List) works"() {
+        setup:
+            InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
+            SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER")
+        when:
+            UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
+                .withUser("user")
+                    .password("password")
+                    .authorities([authority])
+                    .build()
+        then:
+            userDetails.authorities == [authority] as Set
+    }
+}

+ 171 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/AbstractConfiguredSecurityBuilderTests.groovy

@@ -0,0 +1,171 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.SecurityConfigurer
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter
+import org.springframework.test.util.ReflectionTestUtils
+
+import spock.lang.Specification
+
+/**
+ * @author Rob Winch
+ *
+ */
+class AbstractConfiguredSecurityBuilderTests extends Specification {
+
+    ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder()
+
+    def "Null ObjectPostProcessor rejected"() {
+        when:
+            new ConcreteAbstractConfiguredBuilder(null)
+        then:
+            thrown(IllegalArgumentException)
+        when:
+            builder.objectPostProcessor(null);
+        then:
+            thrown(IllegalArgumentException)
+    }
+
+    def "apply null is rejected"() {
+        when:
+            builder.apply(null)
+        then:
+            thrown(IllegalArgumentException)
+    }
+
+    def "Duplicate configurer is removed"() {
+        when:
+            builder.apply(new ConcreteConfigurer())
+            builder.apply(new ConcreteConfigurer())
+        then:
+            ReflectionTestUtils.getField(builder,"configurers").size() == 1
+    }
+
+    def "build twice fails"() {
+        setup:
+            builder.build()
+        when:
+            builder.build()
+        then:
+            thrown(IllegalStateException)
+    }
+
+    def "getObject before build fails"() {
+        when:
+            builder.getObject()
+        then:
+            thrown(IllegalStateException)
+    }
+
+    def "Configurer.init can apply another configurer"() {
+        setup:
+            DelegateConfigurer.CONF = Mock(SecurityConfigurerAdapter)
+        when:
+            builder.apply(new DelegateConfigurer())
+            builder.build()
+        then:
+            1 * DelegateConfigurer.CONF.init(builder)
+            1 * DelegateConfigurer.CONF.configure(builder)
+    }
+
+    def "getConfigurer with multi fails"() {
+        setup:
+            ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
+            builder.apply(new DelegateConfigurer())
+            builder.apply(new DelegateConfigurer())
+        when:
+            builder.getConfigurer(DelegateConfigurer)
+        then: "Fail due to trying to obtain a single DelegateConfigurer and multiple are provided"
+            thrown(IllegalStateException)
+    }
+
+    def "removeConfigurer with multi fails"() {
+        setup:
+            ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
+            builder.apply(new DelegateConfigurer())
+            builder.apply(new DelegateConfigurer())
+        when:
+            builder.removeConfigurer(DelegateConfigurer)
+        then: "Fail due to trying to remove and obtain a single DelegateConfigurer and multiple are provided"
+            thrown(IllegalStateException)
+    }
+
+    def "removeConfigurers with multi"() {
+        setup:
+            DelegateConfigurer c1 = new DelegateConfigurer()
+            DelegateConfigurer c2 = new DelegateConfigurer()
+            ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
+            builder.apply(c1)
+            builder.apply(c2)
+        when:
+            def result = builder.removeConfigurers(DelegateConfigurer)
+        then:
+            result.size() == 2
+            result.contains(c1)
+            result.contains(c2)
+            builder.getConfigurers(DelegateConfigurer).empty
+    }
+
+    def "getConfigurers with multi"() {
+        setup:
+            DelegateConfigurer c1 = new DelegateConfigurer()
+            DelegateConfigurer c2 = new DelegateConfigurer()
+            ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
+            builder.apply(c1)
+            builder.apply(c2)
+        when:
+            def result = builder.getConfigurers(DelegateConfigurer)
+        then:
+            result.size() == 2
+            result.contains(c1)
+            result.contains(c2)
+            builder.getConfigurers(DelegateConfigurer).size() == 2
+    }
+
+    private static class DelegateConfigurer extends SecurityConfigurerAdapter<Object, ConcreteAbstractConfiguredBuilder> {
+        private static SecurityConfigurer<Object, ConcreteAbstractConfiguredBuilder> CONF;
+
+        @Override
+        public void init(ConcreteAbstractConfiguredBuilder builder)
+                throws Exception {
+            builder.apply(CONF);
+        }
+    }
+
+    private static class ConcreteConfigurer extends SecurityConfigurerAdapter<Object, ConcreteAbstractConfiguredBuilder> { }
+
+    private static class ConcreteAbstractConfiguredBuilder extends AbstractConfiguredSecurityBuilder<Object, ConcreteAbstractConfiguredBuilder> {
+
+        public ConcreteAbstractConfiguredBuilder() {
+        }
+
+        public ConcreteAbstractConfiguredBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
+            super(objectPostProcessor);
+        }
+
+        public ConcreteAbstractConfiguredBuilder(ObjectPostProcessor<Object> objectPostProcessor, boolean allowMulti) {
+            super(objectPostProcessor,allowMulti);
+        }
+
+        public Object performBuild() throws Exception {
+            return "success";
+        }
+    }
+
+}

+ 59 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/RequestMatchersTests.groovy

@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import static org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer.RequestMatchers.*
+
+import org.springframework.http.HttpMethod;
+import org.springframework.security.web.util.AntPathRequestMatcher;
+import org.springframework.security.web.util.RegexRequestMatcher;
+
+import spock.lang.Specification;
+
+/**
+ * @author Rob Winch
+ *
+ */
+class RequestMatchersTests extends Specification {
+
+    def "regexMatchers(GET,'/a.*') uses RegexRequestMatcher"() {
+        when:
+        def matchers = regexMatchers(HttpMethod.GET, "/a.*")
+        then: 'matcher is a RegexRequestMatcher'
+        matchers.collect {it.class } == [RegexRequestMatcher]
+    }
+
+    def "regexMatchers('/a.*') uses RegexRequestMatcher"() {
+        when:
+        def matchers = regexMatchers("/a.*")
+        then: 'matcher is a RegexRequestMatcher'
+        matchers.collect {it.class } == [RegexRequestMatcher]
+    }
+
+    def "antMatchers(GET,'/a.*') uses AntPathRequestMatcher"() {
+        when:
+        def matchers = antMatchers(HttpMethod.GET, "/a.*")
+        then: 'matcher is a RegexRequestMatcher'
+        matchers.collect {it.class } == [AntPathRequestMatcher]
+    }
+
+    def "antMatchers('/a.*') uses AntPathRequestMatcher"() {
+        when:
+        def matchers = antMatchers("/a.*")
+        then: 'matcher is a AntPathRequestMatcher'
+        matchers.collect {it.class } == [AntPathRequestMatcher]
+    }
+}

+ 321 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy

@@ -0,0 +1,321 @@
+/*
+ * 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
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.core.annotation.Order
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.config.annotation.BaseWebSpecuritySpec
+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.builders.WebSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+
+/**
+ * Demonstrate the samples
+ *
+ * @author Rob Winch
+ *
+ */
+public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpec {
+    def "README HelloWorld Sample works"() {
+        setup: "Sample Config is loaded"
+            loadConfig(HelloWorldWebSecurityConfigurerAdapter)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getRedirectedUrl() == "http://localhost/login"
+        when: "fail to log in"
+            super.setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to login error page"
+            response.getRedirectedUrl() == "/login?error"
+        when: "login success"
+            super.setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            request.parameters.username = ["user"] as String[]
+            request.parameters.password = ["password"] as String[]
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to default succes page"
+            response.getRedirectedUrl() == "/"
+    }
+
+    /**
+     * <code>
+     *   <http use-expressions="true">
+     *     <intercept-url pattern="/resources/**" access="permitAll"/>
+     *     <intercept-url pattern="/**" access="authenticated"/>
+     *     <logout
+     *         logout-success-url="/login?logout"
+     *         logout-url="/logout"
+     *     <form-login
+     *         authentication-failure-url="/login?error"
+     *         login-page="/login" <!-- Except Spring Security renders the login page -->
+     *         login-processing-url="/login" <!-- but only POST -->
+     *         password-parameter="password"
+     *         username-parameter="username"
+     *     />
+     *   </http>
+     *   <authentication-manager>
+     *     <authentication-provider>
+     *       <user-service>
+     *         <user username="user" password="password" authorities="ROLE_USER"/>
+     *       </user-service>
+     *     </authentication-provider>
+     *   </authentication-manager>
+     * </code>
+     * @author Rob Winch
+     */
+    @Configuration
+    @EnableWebSecurity
+    public static class HelloWorldWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER");
+        }
+    }
+
+    def "README Sample works"() {
+        setup: "Sample Config is loaded"
+            loadConfig(SampleWebSecurityConfigurerAdapter)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getRedirectedUrl() == "http://localhost/login"
+        when: "fail to log in"
+            super.setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to login error page"
+            response.getRedirectedUrl() == "/login?error"
+        when: "login success"
+            super.setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            request.parameters.username = ["user"] as String[]
+            request.parameters.password = ["password"] as String[]
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to default succes page"
+            response.getRedirectedUrl() == "/"
+    }
+
+    /**
+     * <code>
+     *   <http security="none" pattern="/resources/**"/>
+     *   <http use-expressions="true">
+     *     <intercept-url pattern="/logout" access="permitAll"/>
+     *     <intercept-url pattern="/login" access="permitAll"/>
+     *     <intercept-url pattern="/signup" access="permitAll"/>
+     *     <intercept-url pattern="/about" access="permitAll"/>
+     *     <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
+     *     <logout
+     *         logout-success-url="/login?logout"
+     *         logout-url="/logout"
+     *     <form-login
+     *         authentication-failure-url="/login?error"
+     *         login-page="/login"
+     *         login-processing-url="/login" <!-- but only POST -->
+     *         password-parameter="password"
+     *         username-parameter="username"
+     *     />
+     *   </http>
+     *   <authentication-manager>
+     *     <authentication-provider>
+     *       <user-service>
+     *         <user username="user" password="password" authorities="ROLE_USER"/>
+     *         <user username="admin" password="password" authorities="ROLE_USER,ROLE_ADMIN"/>
+     *       </user-service>
+     *     </authentication-provider>
+     *   </authentication-manager>
+     * </code>
+     * @author Rob Winch
+     */
+    @Configuration
+    @EnableWebSecurity
+    public static class SampleWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
+
+        @Override
+        public void configure(WebSecurity web) throws Exception {
+            web
+                .ignoring()
+                    .antMatchers("/resources/**");
+        }
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .antMatchers("/signup","/about").permitAll()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+                    .loginUrl("/login")
+                    // set permitAll for all URLs associated with Form Login
+                    .permitAll();
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth) {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER").and()
+                    .withUser("admin").password("password").roles("USER", "ADMIN");
+        }
+    }
+
+    def "README Multi http Sample works"() {
+        setup:
+            loadConfig(SampleMultiHttpSecurityConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getRedirectedUrl() == "http://localhost/login"
+        when: "fail to log in"
+            super.setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to login error page"
+            response.getRedirectedUrl() == "/login?error"
+        when: "login success"
+            super.setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            request.parameters.username = ["user"] as String[]
+            request.parameters.password = ["password"] as String[]
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to default succes page"
+            response.getRedirectedUrl() == "/"
+
+        when: "request protected API URL"
+            super.setup()
+            request.servletPath = "/api/admin/test"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "get 403"
+            response.getStatus() == 403
+
+        when: "request API for admins with user"
+            super.setup()
+            request.servletPath = "/api/admin/test"
+            request.addHeader("Authorization", "Basic " + "user:password".bytes.encodeBase64().toString())
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "get 403"
+            response.getStatus() == 403
+
+        when: "request API for admins with admin"
+            super.setup()
+            request.servletPath = "/api/admin/test"
+            request.addHeader("Authorization", "Basic " + "admin:password".bytes.encodeBase64().toString())
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "get 200"
+            response.getStatus() == 200
+    }
+
+
+    /**
+     * <code>
+     *   <http security="none" pattern="/resources/**"/>
+     *   <http use-expressions="true" pattern="/api/**">
+     *     <intercept-url pattern="/api/admin/**" access="hasRole('ROLE_ADMIN')"/>
+     *     <intercept-url pattern="/api/**" access="hasRole('ROLE_USER')"/>
+     *     <http-basic />
+     *   </http>
+     *   <http use-expressions="true">
+     *     <intercept-url pattern="/logout" access="permitAll"/>
+     *     <intercept-url pattern="/login" access="permitAll"/>
+     *     <intercept-url pattern="/signup" access="permitAll"/>
+     *     <intercept-url pattern="/about" access="permitAll"/>
+     *     <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
+     *     <logout
+     *         logout-success-url="/login?logout"
+     *         logout-url="/logout"
+     *     <form-login
+     *         authentication-failure-url="/login?error"
+     *         login-page="/login"
+     *         login-processing-url="/login" <!-- but only POST -->
+     *         password-parameter="password"
+     *         username-parameter="username"
+     *     />
+     *   </http>
+     *   <authentication-manager>
+     *     <authentication-provider>
+     *       <user-service>
+     *         <user username="user" password="password" authorities="ROLE_USER"/>
+     *         <user username="admin" password="password" authorities="ROLE_USER,ROLE_ADMIN"/>
+     *       </user-service>
+     *     </authentication-provider>
+     *   </authentication-manager>
+     * </code>
+     * @author Rob Winch
+     */
+    @Configuration
+    @EnableWebSecurity
+    public static class SampleMultiHttpSecurityConfig {
+        @Bean
+        public AuthenticationManager authenticationManager() {
+            return new AuthenticationManagerBuilder()
+                    .inMemoryAuthentication()
+                        .withUser("user").password("password").roles("USER").and()
+                        .withUser("admin").password("password").roles("USER", "ADMIN").and()
+                        .and()
+                    .build();
+        }
+
+        @Configuration
+        @Order(1)
+        public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .antMatcher("/api/**")
+                    .authorizeUrls()
+                        .antMatchers("/api/admin/**").hasRole("ADMIN")
+                        .antMatchers("/api/**").hasRole("USER")
+                        .and()
+                    .httpBasic();
+            }
+        }
+
+        @Configuration
+        public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
+            @Override
+            public void configure(WebSecurity web) throws Exception {
+                web
+                    .ignoring()
+                        .antMatchers("/resources/**");
+            }
+
+            @Override
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .authorizeUrls()
+                        .antMatchers("/signup","/about").permitAll()
+                        .anyRequest().hasRole("USER")
+                        .and()
+                    .formLogin()
+                        .loginUrl("/login")
+                        .permitAll();
+            }
+        }
+    }
+}

+ 102 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy

@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import static org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapterTestsConfigs.*
+import static org.junit.Assert.*
+
+import javax.sql.DataSource
+
+import org.springframework.context.ApplicationContext
+import org.springframework.context.ApplicationListener
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
+import org.springframework.ldap.core.support.BaseLdapPathContextSource
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.AuthenticationProvider
+import org.springframework.security.authentication.DefaultAuthenticationEventPublisher
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.authentication.event.AuthenticationSuccessEvent
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.authority.AuthorityUtils
+import org.springframework.security.ldap.DefaultSpringSecurityContextSource
+
+/**
+ * @author Rob Winch
+ *
+ */
+class WebSecurityConfigurerAdapterTests extends BaseSpringSpec {
+
+    def "MessageSources populated on AuthenticationProviders"() {
+        when:
+            loadConfig(MessageSourcesPopulatedConfig)
+            List<AuthenticationProvider> providers = authenticationProviders()
+        then:
+            providers*.messages*.messageSource == [context,context,context,context]
+    }
+
+    def "messages set when using WebSecurityConfigurerAdapter"() {
+        when:
+            loadConfig(InMemoryAuthWithWebSecurityConfigurerAdapter)
+        then:
+            authenticationManager.messages.messageSource instanceof ApplicationContext
+    }
+
+    def "AuthenticationEventPublisher is registered for Web registerAuthentication"() {
+        when:
+            loadConfig(InMemoryAuthWithWebSecurityConfigurerAdapter)
+        then:
+            authenticationManager.parent.eventPublisher instanceof DefaultAuthenticationEventPublisher
+        when:
+            Authentication token = new UsernamePasswordAuthenticationToken("user","password")
+            authenticationManager.authenticate(token)
+        then: "We only receive the AuthenticationSuccessEvent once"
+            InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS.size() == 1
+            InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS[0].authentication.name == token.principal
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class InMemoryAuthWithWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements ApplicationListener<AuthenticationSuccessEvent> {
+        static List<AuthenticationSuccessEvent> EVENTS = []
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER")
+        }
+
+        @Override
+        public void onApplicationEvent(AuthenticationSuccessEvent e) {
+            EVENTS.add(e)
+        }
+    }
+}

+ 76 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTestsConfigs.java

@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import javax.sql.DataSource;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+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.ldap.DefaultSpringSecurityContextSource;
+
+/**
+ * @author Rob Winch
+ *
+ */
+public class WebSecurityConfigurerAdapterTestsConfigs {
+
+    // necessary because groovy resolves incorrect method when using generics
+    @Configuration
+    @EnableWebSecurity
+    static class MessageSourcesPopulatedConfig extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .antMatcher("/role1/**")
+                .authorizeUrls()
+                    .anyRequest().hasRole("1");
+        }
+
+        @Bean
+        public BaseLdapPathContextSource contextSource() throws Exception {
+            DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:33389/dc=springframework,dc=org");
+            contextSource.setUserDn("uid=admin,ou=system");
+            contextSource.setPassword("secret");
+            return contextSource;
+        }
+
+        @Bean
+        public DataSource dataSource() {
+            EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
+            return builder.setType(EmbeddedDatabaseType.HSQL).build();
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication().and()
+                .jdbcAuthentication()
+                    .dataSource(dataSource())
+                    .and()
+                .ldapAuthentication()
+                    .userDnPatterns("uid={0},ou=people")
+                    .contextSource(contextSource());
+        }
+    }
+}

+ 120 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/builders/HttpConfigurationTests.groovy

@@ -0,0 +1,120 @@
+/*
+ * 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.builders
+
+import javax.servlet.Filter
+import javax.servlet.FilterChain
+import javax.servlet.ServletException
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+import org.springframework.beans.factory.BeanCreationException
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockFilterChain
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.security.cas.web.CasAuthenticationFilter
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.web.configuration.BaseWebConfig
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+import org.springframework.web.filter.OncePerRequestFilter
+
+/**
+ * HttpSecurity tests
+ *
+ * @author Rob Winch
+ *
+ */
+public class HttpSecurityTests extends BaseSpringSpec {
+    def "addFilter with unregistered Filter"() {
+        when:
+            loadConfig(UnregisteredFilterConfig)
+        then:
+            BeanCreationException success = thrown()
+            success.message.contains "The Filter class ${UnregisteredFilter.name} does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."
+    }
+
+    @Configuration
+    static class UnregisteredFilterConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .addFilter(new UnregisteredFilter())
+        }
+    }
+
+    static class UnregisteredFilter extends OncePerRequestFilter {
+        @Override
+        protected void doFilterInternal(HttpServletRequest request,
+                HttpServletResponse response, FilterChain filterChain)
+                throws ServletException, IOException {
+            filterChain.doFilter(request, response);
+        }
+    }
+
+    // https://github.com/SpringSource/spring-security-javaconfig/issues/104
+    def "#104 addFilter CasAuthenticationFilter"() {
+        when:
+            loadConfig(CasAuthenticationFilterConfig)
+        then:
+            findFilter(CasAuthenticationFilter)
+    }
+
+    @Configuration
+    static class CasAuthenticationFilterConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .addFilter(new CasAuthenticationFilter())
+        }
+    }
+
+
+    def "requestMatchers() javadoc"() {
+        setup: "load configuration like the config on the requestMatchers() javadoc"
+            loadConfig(RequestMatcherRegistryConfigs)
+        when:
+            super.setup()
+            request.servletPath = "/oauth/a"
+            springSecurityFilterChain.doFilter(request, response, chain)
+        then:
+            response.status == 403
+        where:
+            servletPath | status
+            "/oauth/a"  | 403
+            "/oauth/b"  | 403
+            "/api/a"    | 403
+            "/api/b"    | 403
+            "/oauth2/b" | 200
+            "/api2/b"   | 200
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class RequestMatcherRegistryConfigs extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+              .requestMatchers()
+                  .antMatchers("/api/**")
+                  .antMatchers("/oauth/**")
+                  .and()
+              .authorizeUrls()
+                  .antMatchers("/**").hasRole("USER")
+                  .and()
+              .httpBasic()
+        }
+    }
+}

+ 511 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.groovy

@@ -0,0 +1,511 @@
+/*
+ * 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.builders
+
+import javax.servlet.http.HttpServletRequest
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.access.AccessDecisionManager
+import org.springframework.security.access.ConfigAttribute
+import org.springframework.security.access.vote.AuthenticatedVoter
+import org.springframework.security.access.vote.RoleVoter
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.BadCredentialsException
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.web.builders.NamespaceHttpTests.AuthenticationManagerRefConfig.CustomAuthenticationManager
+import org.springframework.security.config.annotation.web.builders.NamespaceHttpTests.RequestMatcherRefConfig.MyRequestMatcher
+import org.springframework.security.config.annotation.web.configuration.BaseWebConfig
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configurers.SessionCreationPolicy
+import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.AuthenticationException
+import org.springframework.security.web.FilterInvocation
+import org.springframework.security.web.access.ExceptionTranslationFilter
+import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource
+import org.springframework.security.web.access.expression.WebExpressionVoter
+import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
+import org.springframework.security.web.context.HttpSessionSecurityContextRepository
+import org.springframework.security.web.context.NullSecurityContextRepository
+import org.springframework.security.web.context.SecurityContextPersistenceFilter
+import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache
+import org.springframework.security.web.savedrequest.NullRequestCache
+import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
+import org.springframework.security.web.session.SessionManagementFilter
+import org.springframework.security.web.util.RegexRequestMatcher
+import org.springframework.security.web.util.RequestMatcher
+
+/**
+ * Tests to verify that all the functionality of <http> attributes is present
+ *
+ * @author Rob Winch
+ *
+ */
+public class NamespaceHttpTests extends BaseSpringSpec {
+    def "http@access-decision-manager-ref"() {
+        setup:
+            AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR = Mock(AccessDecisionManager)
+            AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR.supports(FilterInvocation) >> true
+            AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR.supports(_ as ConfigAttribute) >> true
+        when:
+            loadConfig(AccessDecisionManagerRefConfig)
+        then:
+            findFilter(FilterSecurityInterceptor).accessDecisionManager == AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR
+    }
+
+    @Configuration
+    static class AccessDecisionManagerRefConfig extends BaseWebConfig {
+        static AccessDecisionManager ACCESS_DECISION_MGR
+
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .anyRequest().permitAll()
+                    .accessDecisionManager(ACCESS_DECISION_MGR)
+        }
+    }
+
+    def "http@access-denied-page"() {
+        when:
+            loadConfig(AccessDeniedPageConfig)
+        then:
+            findFilter(ExceptionTranslationFilter).accessDeniedHandler.errorPage == "/AccessDeniedPageConfig"
+    }
+
+    @Configuration
+    static class AccessDeniedPageConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .exceptionHandling()
+                    .accessDeniedPage("/AccessDeniedPageConfig")
+        }
+    }
+
+    def "http@authentication-manager-ref"() {
+        when: "Specify AuthenticationManager"
+            loadConfig(AuthenticationManagerRefConfig)
+        then: "Populates the AuthenticationManager"
+            findFilter(FilterSecurityInterceptor).authenticationManager.parent.class == CustomAuthenticationManager
+    }
+
+    @Configuration
+    static class AuthenticationManagerRefConfig extends BaseWebConfig {
+        // demo authentication-manager-ref (could be any value)
+
+        @Override
+        protected AuthenticationManager authenticationManager() throws Exception {
+            return new CustomAuthenticationManager();
+        }
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER");
+        }
+
+        static class CustomAuthenticationManager implements AuthenticationManager {
+            public Authentication authenticate(Authentication authentication)
+                    throws AuthenticationException {
+                throw new BadCredentialsException("This always fails");
+            }
+        }
+    }
+
+    // Note: There is no http@auto-config equivalent in Java Config
+
+    def "http@create-session=always"() {
+        when:
+            loadConfig(IfRequiredConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
+            findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
+            findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
+            findFilter(ExceptionTranslationFilter).requestCache.class == HttpSessionRequestCache
+    }
+
+    @Configuration
+    static class CreateSessionAlwaysConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .sessionManagement()
+                    .sessionCreationPolicy(SessionCreationPolicy.always);
+        }
+    }
+
+    def "http@create-session=stateless"() {
+        when:
+            loadConfig(CreateSessionStatelessConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
+            findFilter(SecurityContextPersistenceFilter).repo.class == NullSecurityContextRepository
+            findFilter(SessionManagementFilter).securityContextRepository.class == NullSecurityContextRepository
+            findFilter(ExceptionTranslationFilter).requestCache.class == NullRequestCache
+            findFilter(RequestCacheAwareFilter).requestCache.class == NullRequestCache
+    }
+
+    @Configuration
+    static class CreateSessionStatelessConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .sessionManagement()
+                    .sessionCreationPolicy(SessionCreationPolicy.stateless);
+        }
+    }
+
+    def "http@create-session=ifRequired"() {
+        when:
+            loadConfig(IfRequiredConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
+            findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
+            findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
+    }
+
+    @Configuration
+    static class IfRequiredConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .sessionManagement()
+                    .sessionCreationPolicy(SessionCreationPolicy.ifRequired);
+        }
+    }
+
+    def "http@create-session defaults to ifRequired"() {
+        when:
+            loadConfig(IfRequiredConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
+            findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
+            findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
+    }
+
+    def "http@create-session=never"() {
+        when:
+            loadConfig(CreateSessionNeverConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
+            findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == false
+            findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == false
+    }
+
+    @Configuration
+    static class CreateSessionNeverConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .sessionManagement()
+                    .sessionCreationPolicy(SessionCreationPolicy.never);
+        }
+    }
+
+    @Configuration
+    static class DefaultCreateSessionConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+        }
+    }
+
+    def "http@disable-url-rewriting = true (default for Java Config)"() {
+        when:
+            loadConfig(DefaultUrlRewritingConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting
+    }
+
+    @Configuration
+    static class DefaultUrlRewritingConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+        }
+    }
+
+    // http@disable-url-rewriting is on by default to disable it create a custom HttpSecurityContextRepository and use security-context-repository-ref
+
+    def "http@disable-url-rewriting = false"() {
+        when:
+            loadConfig(EnableUrlRewritingConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting == false
+    }
+
+    @Configuration
+    static class EnableUrlRewritingConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository()
+            repository.disableUrlRewriting = false // explicitly configured (not necessary due to default values)
+
+            http.
+                securityContext()
+                    .securityContextRepository(repository)
+        }
+    }
+
+    def "http@entry-point-ref"() {
+        when:
+            loadConfig(EntryPointRefConfig)
+        then:
+            findFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == "/EntryPointRefConfig"
+    }
+
+    @Configuration
+    static class EntryPointRefConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .exceptionHandling()
+                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/EntryPointRefConfig"))
+        }
+    }
+
+    def "http@jaas-api-provision"() {
+        when:
+            loadConfig(JaasApiProvisionConfig)
+        then:
+            findFilter(JaasApiIntegrationFilter)
+    }
+
+    @Configuration
+    static class JaasApiProvisionConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .addFilter(new JaasApiIntegrationFilter())
+        }
+    }
+
+    // http@name is not available since it can be done w/ standard bean configuration easily
+
+    def "http@once-per-request=true"() {
+        when:
+            loadConfig(OncePerRequestConfig)
+        then:
+            findFilter(FilterSecurityInterceptor).observeOncePerRequest
+    }
+
+    @Configuration
+    static class OncePerRequestConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER");
+        }
+    }
+
+    def "http@once-per-request=false"() {
+        when:
+            loadConfig(OncePerRequestFalseConfig)
+        then:
+            !findFilter(FilterSecurityInterceptor).observeOncePerRequest
+    }
+
+    @Configuration
+    static class OncePerRequestFalseConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http.
+                authorizeUrls()
+                    .filterSecurityInterceptorOncePerRequest(false)
+                    .antMatchers("/users**","/sessions/**").hasRole("ADMIN")
+                    .antMatchers("/signup").permitAll()
+                    .anyRequest().hasRole("USER");
+        }
+    }
+
+    // http@path-type is not available (instead request matcher instances are used)
+
+    // http@pattern is not available (instead see the tests http@request-matcher-ref ant or http@request-matcher-ref regex)
+
+    def "http@realm"() {
+        when:
+            loadConfig(RealmConfig)
+        then:
+            findFilter(BasicAuthenticationFilter).authenticationEntryPoint.realmName == "RealmConfig"
+    }
+
+    @Configuration
+    static class RealmConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic().realmName("RealmConfig")
+        }
+    }
+
+    // http@request-matcher is not available (instead request matcher instances are used)
+
+    def "http@request-matcher-ref ant"() {
+        when:
+            loadConfig(RequestMatcherAntConfig)
+        then:
+            filterChain(0).requestMatcher.pattern == "/api/**"
+    }
+
+    @Configuration
+    static class RequestMatcherAntConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .antMatcher("/api/**")
+        }
+    }
+
+    def "http@request-matcher-ref regex"() {
+        when:
+            loadConfig(RequestMatcherRegexConfig)
+        then:
+            filterChain(0).requestMatcher.class == RegexRequestMatcher
+            filterChain(0).requestMatcher.pattern.matcher("/regex/a")
+            filterChain(0).requestMatcher.pattern.matcher("/regex/b")
+            !filterChain(0).requestMatcher.pattern.matcher("/regex1/b")
+    }
+
+    @Configuration
+    static class RequestMatcherRegexConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .regexMatcher("/regex/.*")
+        }
+    }
+
+    def "http@request-matcher-ref"() {
+        when:
+            loadConfig(RequestMatcherRefConfig)
+        then:
+            filterChain(0).requestMatcher.class == MyRequestMatcher
+    }
+
+    @Configuration
+    static class RequestMatcherRefConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .requestMatcher(new MyRequestMatcher());
+        }
+        static class MyRequestMatcher implements RequestMatcher {
+            public boolean matches(HttpServletRequest request) {
+                return true;
+            }
+        }
+    }
+
+    def "http@security=none"() {
+        when:
+            loadConfig(SecurityNoneConfig)
+        then:
+            filterChain(0).requestMatcher.pattern == "/resources/**"
+            filterChain(0).filters.empty
+            filterChain(1).requestMatcher.pattern == "/public/**"
+            filterChain(1).filters.empty
+    }
+
+    @Configuration
+    static class SecurityNoneConfig extends BaseWebConfig {
+
+        @Override
+        public void configure(WebSecurity web)
+                throws Exception {
+            web
+                .ignoring()
+                    .antMatchers("/resources/**","/public/**")
+        }
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {}
+
+    }
+
+    def "http@security-context-repository-ref"() {
+        when:
+            loadConfig(SecurityContextRepoConfig)
+        then:
+            findFilter(SecurityContextPersistenceFilter).repo.class == NullSecurityContextRepository
+    }
+
+    @Configuration
+    static class SecurityContextRepoConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .securityContext()
+                    .securityContextRepository(new NullSecurityContextRepository()) // security-context-repository-ref
+        }
+    }
+
+    def "http@servlet-api-provision=false"() {
+        when:
+            loadConfig(ServletApiProvisionConfig)
+        then:
+            findFilter(SecurityContextHolderAwareRequestFilter) == null
+    }
+
+    @Configuration
+    static class ServletApiProvisionConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http.servletApi().disable()
+        }
+    }
+
+    def "http@servlet-api-provision defaults to true"() {
+        when:
+            loadConfig(ServletApiProvisionDefaultsConfig)
+        then:
+            findFilter(SecurityContextHolderAwareRequestFilter) != null
+    }
+
+    @Configuration
+    static class ServletApiProvisionDefaultsConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+        }
+    }
+
+    def "http@use-expressions=true"() {
+        when:
+            loadConfig(UseExpressionsConfig)
+        then:
+            findFilter(FilterSecurityInterceptor).securityMetadataSource.class == ExpressionBasedFilterInvocationSecurityMetadataSource
+            findFilter(FilterSecurityInterceptor).accessDecisionManager.decisionVoters.collect { it.class } == [WebExpressionVoter]
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class UseExpressionsConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .antMatchers("/users**","/sessions/**").hasRole("USER")
+                    .antMatchers("/signup").permitAll()
+                    .anyRequest().hasRole("USER")
+        }
+    }
+
+    def "http@use-expressions=false"() {
+        when:
+            loadConfig(DisableUseExpressionsConfig)
+        then:
+            findFilter(FilterSecurityInterceptor).securityMetadataSource.class == DefaultFilterInvocationSecurityMetadataSource
+            findFilter(FilterSecurityInterceptor).accessDecisionManager.decisionVoters.collect { it.class } == [RoleVoter, AuthenticatedVoter]
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class DisableUseExpressionsConfig extends BaseWebConfig {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .apply(new UrlAuthorizationConfigurer())
+                    .antMatchers("/users**","/sessions/**").hasRole("USER")
+                    .antMatchers("/signup").hasRole("ANONYMOUS")
+                    .anyRequest().hasRole("USER")
+        }
+    }
+}

+ 42 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configuration/BaseWebConfig.groovy

@@ -0,0 +1,42 @@
+/*
+ * 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.configuration;
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+
+/**
+ *
+ * @author Rob Winch
+ */
+@Configuration
+@EnableWebSecurity
+public abstract class BaseWebConfig extends WebSecurityConfigurerAdapter {
+    BaseWebConfig(boolean disableDefaults) {
+        super(disableDefaults)
+    }
+
+    BaseWebConfig() {
+    }
+
+    protected void registerAuthentication(
+                AuthenticationManagerBuilder auth) throws Exception {
+        auth
+            .inMemoryAuthentication()
+                .withUser("user").password("password").roles("USER").and()
+                .withUser("admin").password("password").roles("USER", "ADMIN");
+    }
+}

+ 88 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configuration/EnableWebSecurityTests.groovy

@@ -0,0 +1,88 @@
+/*
+ * 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.configuration;
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.security.authentication.AnonymousAuthenticationToken
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.DebugFilter;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
+
+class EnableWebSecurityTests extends BaseSpringSpec {
+
+    def "@Bean(BeanIds.AUTHENTICATION_MANAGER) includes HttpSecurity's AuthenticationManagerBuilder"() {
+        when:
+            loadConfig(SecurityConfig)
+            AuthenticationManager authenticationManager = context.getBean(AuthenticationManager)
+            AnonymousAuthenticationToken anonymousAuthToken = findFilter(AnonymousAuthenticationFilter).createAuthentication(new MockHttpServletRequest())
+        then:
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
+            authenticationManager.authenticate(anonymousAuthToken)
+
+    }
+
+
+    @EnableWebSecurity
+    @Configuration
+    static class SecurityConfig extends WebSecurityConfigurerAdapter {
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER");
+        }
+
+        @Bean
+        @Override
+        public AuthenticationManager authenticationManagerBean()
+                throws Exception {
+            return super.authenticationManagerBean();
+        }
+
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .antMatchers("/*").hasRole("USER")
+                    .and()
+                .formLogin();
+        }
+    }
+
+    def "@EnableWebSecurity on superclass"() {
+        when:
+            loadConfig(ChildSecurityConfig)
+        then:
+            context.getBean("springSecurityFilterChain", DebugFilter)
+    }
+
+    @Configuration
+    static class ChildSecurityConfig extends DebugSecurityConfig {
+    }
+
+    @Configuration
+    @EnableWebSecurity(debug=true)
+    static class DebugSecurityConfig extends WebSecurityConfigurerAdapter {
+
+    }
+}

+ 217 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.groovy

@@ -0,0 +1,217 @@
+/*
+ * 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.configuration;
+
+import static org.junit.Assert.*
+
+import java.util.List;
+
+import org.springframework.beans.factory.BeanCreationException
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration
+import org.springframework.core.annotation.Order
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
+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.builders.WebSecurity
+import org.springframework.security.web.FilterChainProxy
+import org.springframework.security.web.SecurityFilterChain
+import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
+import org.springframework.security.web.access.expression.WebSecurityExpressionHandler;
+import org.springframework.security.web.util.AnyRequestMatcher
+
+/**
+ * @author Rob Winch
+ *
+ */
+class WebSecurityConfigurationTests extends BaseSpringSpec {
+
+    def "WebSecurityConfigurers are sorted"() {
+        when:
+            loadConfig(SortedWebSecurityConfigurerAdaptersConfig);
+            List<SecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
+        then:
+            filterChains[0].requestMatcher.pattern == "/ignore1"
+            filterChains[0].filters.empty
+            filterChains[1].requestMatcher.pattern == "/ignore2"
+            filterChains[1].filters.empty
+
+            filterChains[2].requestMatcher.pattern == "/role1/**"
+            filterChains[3].requestMatcher.pattern == "/role2/**"
+            filterChains[4].requestMatcher.pattern == "/role3/**"
+            filterChains[5].requestMatcher.class == AnyRequestMatcher
+    }
+
+
+    @Configuration
+    @EnableWebSecurity
+    static class SortedWebSecurityConfigurerAdaptersConfig {
+        public AuthenticationManager authenticationManager() throws Exception {
+            return new AuthenticationManagerBuilder()
+                .inMemoryAuthentication()
+                    .withUser("marissa").password("koala").roles("USER").and()
+                    .withUser("paul").password("emu").roles("USER").and()
+                    .and()
+                .build();
+        }
+
+        @Configuration
+        @Order(1)
+        public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
+            @Override
+            public void configure(WebSecurity web)	throws Exception {
+                web
+                    .ignoring()
+                        .antMatchers("/ignore1","/ignore2");
+            }
+
+            @Override
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .antMatcher("/role1/**")
+                    .authorizeUrls()
+                        .anyRequest().hasRole("1");
+            }
+        }
+
+        @Configuration
+        @Order(2)
+        public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
+            @Override
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .antMatcher("/role2/**")
+                        .authorizeUrls()
+                            .anyRequest().hasRole("2");
+            }
+        }
+
+        @Configuration
+        @Order(3)
+        public static class WebConfigurer3 extends WebSecurityConfigurerAdapter {
+            @Override
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .antMatcher("/role3/**")
+                    .authorizeUrls()
+                        .anyRequest().hasRole("3");
+            }
+        }
+
+        @Configuration
+        public static class WebConfigurer4 extends WebSecurityConfigurerAdapter {
+
+            @Override
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .authorizeUrls()
+                        .anyRequest().hasRole("4");
+            }
+        }
+    }
+
+    def "WebSecurityConfigurers fails with duplicate order"() {
+        when:
+            loadConfig(DuplicateOrderConfig);
+        then:
+            BeanCreationException e = thrown()
+            e.message.contains "@Order on WebSecurityConfigurers must be unique"
+    }
+
+
+    @Configuration
+    @EnableWebSecurity
+    static class DuplicateOrderConfig {
+        public AuthenticationManager authenticationManager() throws Exception {
+            return new AuthenticationManagerBuilder()
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER").and()
+                    .and()
+                .build();
+        }
+
+        @Configuration
+        public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
+
+            @Override
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .antMatcher("/role1/**")
+                    .authorizeUrls()
+                        .anyRequest().hasRole("1");
+            }
+        }
+
+        @Configuration
+        public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
+            @Override
+            protected void configure(HttpSecurity http) throws Exception {
+                http
+                    .antMatcher("/role2/**")
+                    .authorizeUrls()
+                        .anyRequest().hasRole("2");
+            }
+        }
+    }
+
+    def "Override privilegeEvaluator"() {
+        setup:
+            WebInvocationPrivilegeEvaluator privilegeEvaluator = Mock()
+            PrivilegeEvaluatorConfigurerAdapterConfig.PE = privilegeEvaluator
+        when:
+            loadConfig(PrivilegeEvaluatorConfigurerAdapterConfig)
+        then:
+            context.getBean(WebInvocationPrivilegeEvaluator) == privilegeEvaluator
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class PrivilegeEvaluatorConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
+        static WebInvocationPrivilegeEvaluator PE
+
+        @Override
+        public void configure(WebSecurity web) throws Exception {
+            web
+                .privilegeEvaluator(PE)
+        }
+    }
+
+    def "Override webSecurityExpressionHandler"() {
+        setup:
+            WebSecurityExpressionHandler expressionHandler = Mock()
+            WebSecurityExpressionHandlerConfig.EH = expressionHandler
+        when:
+            loadConfig(WebSecurityExpressionHandlerConfig)
+        then:
+            context.getBean(WebSecurityExpressionHandler) == expressionHandler
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class WebSecurityExpressionHandlerConfig extends WebSecurityConfigurerAdapter {
+        @SuppressWarnings("deprecation")
+        static WebSecurityExpressionHandler EH
+
+        @Override
+        public void configure(WebSecurity web) throws Exception {
+            web
+                .expressionHandler(EH)
+        }
+    }
+}

+ 76 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurerTests.groovy

@@ -0,0 +1,76 @@
+/*
+ * 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 java.util.List;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
+import org.springframework.security.web.util.AntPathRequestMatcher;
+import org.springframework.security.web.util.RegexRequestMatcher;
+import org.springframework.security.web.util.RequestMatcher;
+
+import spock.lang.Specification;
+
+/**
+ * @author Rob Winch
+ *
+ */
+class AbstractRequestMatcherMappingConfigurerTests extends Specification {
+    ConcreteAbstractRequestMatcherMappingConfigurer registry = new ConcreteAbstractRequestMatcherMappingConfigurer()
+
+    def "regexMatchers(GET,'/a.*') uses RegexRequestMatcher"() {
+        when:
+        def matchers = registry.regexMatchers(HttpMethod.GET,"/a.*")
+        then: 'matcher is a RegexRequestMatcher'
+        matchers.collect {it.class } == [RegexRequestMatcher]
+    }
+
+    def "regexMatchers('/a.*') uses RegexRequestMatcher"() {
+        when:
+        def matchers = registry.regexMatchers("/a.*")
+        then: 'matcher is a RegexRequestMatcher'
+        matchers.collect {it.class } == [RegexRequestMatcher]
+    }
+
+    def "antMatchers(GET,'/a.*') uses AntPathRequestMatcher"() {
+        when:
+        def matchers = registry.antMatchers(HttpMethod.GET, "/a.*")
+        then: 'matcher is a RegexRequestMatcher'
+        matchers.collect {it.class } == [AntPathRequestMatcher]
+    }
+
+    def "antMatchers('/a.*') uses AntPathRequestMatcher"() {
+        when:
+        def matchers = registry.antMatchers("/a.*")
+        then: 'matcher is a AntPathRequestMatcher'
+        matchers.collect {it.class } == [AntPathRequestMatcher]
+    }
+
+    static class ConcreteAbstractRequestMatcherMappingConfigurer extends AbstractRequestMatcherMappingConfigurer<HttpSecurity,List<RequestMatcher>,DefaultSecurityFilterChain> {
+        List<AccessDecisionVoter> decisionVoters() {
+            return null;
+        }
+
+        List<RequestMatcher> chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
+            return requestMatchers;
+        }
+    }
+}

+ 53 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.groovy

@@ -0,0 +1,53 @@
+/*
+ * 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.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.web.access.channel.ChannelDecisionManagerImpl
+import org.springframework.security.web.access.channel.ChannelProcessingFilter
+import org.springframework.security.web.access.channel.InsecureChannelProcessor
+import org.springframework.security.web.access.channel.SecureChannelProcessor
+
+/**
+ *
+ * @author Rob Winch
+ */
+class ChannelSecurityConfigurerTests extends BaseSpringSpec {
+
+    def "requiresChannel ObjectPostProcessor"() {
+        setup: "initialize the AUTH_FILTER as a mock"
+            AnyObjectPostProcessor objectPostProcessor = Mock()
+        when:
+            HttpSecurity http = new HttpSecurity(objectPostProcessor, authenticationBldr, [:])
+            http
+                .requiresChannel()
+                    .anyRequest().requiresSecure()
+                    .and()
+                .build()
+
+        then: "InsecureChannelProcessor is registered with LifecycleManager"
+            1 * objectPostProcessor.postProcess(_ as InsecureChannelProcessor) >> {InsecureChannelProcessor o -> o}
+        and: "SecureChannelProcessor is registered with LifecycleManager"
+            1 * objectPostProcessor.postProcess(_ as SecureChannelProcessor) >> {SecureChannelProcessor o -> o}
+        and: "ChannelDecisionManagerImpl is registered with LifecycleManager"
+            1 * objectPostProcessor.postProcess(_ as ChannelDecisionManagerImpl) >> {ChannelDecisionManagerImpl o -> o}
+        and: "ChannelProcessingFilter is registered with LifecycleManager"
+            1 * objectPostProcessor.postProcess(_ as ChannelProcessingFilter) >> {ChannelProcessingFilter o -> o}
+    }
+}

+ 159 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy

@@ -0,0 +1,159 @@
+/*
+ * 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.beans.factory.BeanCreationException
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockFilterChain
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.DefaultSecurityFilterChain
+import org.springframework.security.web.FilterChainProxy
+import org.springframework.security.web.access.ExceptionTranslationFilter
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+import org.springframework.security.web.authentication.logout.LogoutFilter
+import org.springframework.security.web.context.SecurityContextPersistenceFilter
+import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
+import org.springframework.security.web.session.SessionManagementFilter;
+import org.springframework.security.web.util.AnyRequestMatcher
+
+/**
+ *
+ * @author Rob Winch
+ */
+class DefaultFiltersTests extends BaseSpringSpec {
+    def missingConfigMessage = "At least one non-null instance of "+ WebSecurityConfigurer.class.getSimpleName()+" must be exposed as a @Bean when using @EnableWebSecurity. Hint try extending "+ WebSecurityConfigurerAdapter.class.getSimpleName()
+
+    def "DefaultSecurityFilterChainBuilder cannot be null"() {
+        when:
+        context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderMissingConfig)
+        then:
+        BeanCreationException e = thrown()
+        e.message.contains missingConfigMessage
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class FilterChainProxyBuilderMissingConfig { }
+
+    def "FilterChainProxyBuilder no DefaultSecurityFilterChainBuilder specified"() {
+        when:
+        context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderNoSecurityFilterBuildersConfig)
+        then:
+        BeanCreationException e = thrown()
+        e.message.contains missingConfigMessage
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class FilterChainProxyBuilderNoSecurityFilterBuildersConfig {
+        @Bean
+        public WebSecurity filterChainProxyBuilder() {
+            new WebSecurity()
+                .ignoring()
+                    .antMatchers("/resources/**")
+        }
+    }
+
+    def "null WebInvocationPrivilegeEvaluator"() {
+        when:
+        context = new AnnotationConfigApplicationContext(NullWebInvocationPrivilegeEvaluatorConfig)
+        then:
+        List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
+        filterChains.size() == 1
+        filterChains[0].requestMatcher instanceof AnyRequestMatcher
+        filterChains[0].filters.size() == 1
+        filterChains[0].filters.find { it instanceof UsernamePasswordAuthenticationFilter }
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class NullWebInvocationPrivilegeEvaluatorConfig extends BaseWebConfig {
+        NullWebInvocationPrivilegeEvaluatorConfig() {
+            super(true)
+        }
+
+        protected void configure(HttpSecurity http) {
+            http.formLogin()
+        }
+    }
+
+    def "FilterChainProxyBuilder ignoring resources"() {
+        when:
+        context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderIgnoringConfig)
+        then:
+        List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
+        filterChains.size() == 2
+        filterChains[0].requestMatcher.pattern == '/resources/**'
+        filterChains[0].filters.empty
+        filterChains[1].requestMatcher instanceof AnyRequestMatcher
+        filterChains[1].filters.collect { it.class } ==
+                [SecurityContextPersistenceFilter, LogoutFilter, RequestCacheAwareFilter,
+                 SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
+                 ExceptionTranslationFilter, FilterSecurityInterceptor ]
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class FilterChainProxyBuilderIgnoringConfig extends BaseWebConfig {
+        @Override
+        public void configure(WebSecurity builder)	throws Exception {
+            builder
+                .ignoring()
+                    .antMatchers("/resources/**");
+        }
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER");
+        }
+    }
+
+   def "DefaultFilters.permitAll()"() {
+        when:
+        context = new AnnotationConfigApplicationContext(DefaultFiltersConfigPermitAll)
+        then:
+        FilterChainProxy filterChain = context.getBean(FilterChainProxy)
+
+        expect:
+        MockHttpServletResponse response = new MockHttpServletResponse()
+        filterChain.doFilter(new MockHttpServletRequest(servletPath : uri, queryString: query), response, new MockFilterChain())
+        response.redirectedUrl == null
+        where:
+        uri | query
+        "/logout" | null
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class DefaultFiltersConfigPermitAll extends BaseWebConfig {
+        protected void configure(HttpSecurity http) {
+        }
+    }
+}

+ 343 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy

@@ -0,0 +1,343 @@
+
+
+/*
+ * 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 javax.servlet.http.HttpSession
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockFilterChain
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.security.config.annotation.AnyObjectPostProcessor
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
+import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
+import org.springframework.security.web.FilterChainProxy
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
+import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+
+/**
+ * Tests to verify that {@link DefaultLoginPageConfigurer} works
+ *
+ * @author Rob Winch
+ *
+ */
+public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
+    FilterChainProxy springSecurityFilterChain
+    MockHttpServletRequest request
+    MockHttpServletResponse response
+    MockFilterChain chain
+
+    def setup() {
+        request = new MockHttpServletRequest(method:"GET")
+        response = new MockHttpServletResponse()
+        chain = new MockFilterChain()
+    }
+
+    def "http/form-login default login generating page"() {
+        setup:
+            loadConfig(DefaultLoginPageConfig)
+            springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            findFilter(DefaultLoginPageViewFilter)
+            response.getRedirectedUrl() == "http://localhost/login"
+        when: "request the login page"
+            setup()
+            request.requestURI = "/login"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
+<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
+ <table>
+    <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
+    <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
+    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
+  </table>
+</form></body></html>"""
+        when: "fail to log in"
+            setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to login error page"
+            response.getRedirectedUrl() == "/login?error"
+        when: "request the error page"
+            HttpSession session = request.session
+            setup()
+            request.session = session
+            request.requestURI = "/login"
+            request.queryString = "error"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
+<p><font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: Bad credentials</font></p><h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
+ <table>
+    <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
+    <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
+    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
+  </table>
+</form></body></html>"""
+        when: "login success"
+            setup()
+            request.requestURI = "/login"
+            request.method = "POST"
+            request.parameters.username = ["user"] as String[]
+            request.parameters.password = ["password"] as String[]
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to default succes page"
+            response.getRedirectedUrl() == "/"
+    }
+
+    def "logout success renders"() {
+        setup:
+            loadConfig(DefaultLoginPageConfig)
+            springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when: "logout success"
+            request.requestURI = "/login"
+            request.queryString = "logout"
+            request.method = "GET"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "sent to default success page"
+            response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
+<p><font color='green'>You have been logged out</font></p><h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
+ <table>
+    <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
+    <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
+    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
+  </table>
+</form></body></html>"""
+    }
+
+    @Configuration
+    static class DefaultLoginPageConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+        }
+    }
+
+    def "custom logout success handler prevents rendering"() {
+        setup:
+            loadConfig(DefaultLoginPageCustomLogoutSuccessHandlerConfig)
+            springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when: "logout success"
+            request.requestURI = "/login"
+            request.queryString = "logout"
+            request.method = "GET"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "default success page is NOT rendered (application is in charge of it)"
+            response.getContentAsString() == ""
+    }
+
+    @Configuration
+    static class DefaultLoginPageCustomLogoutSuccessHandlerConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .logout()
+                    .logoutSuccessHandler(new SimpleUrlLogoutSuccessHandler())
+                    .and()
+                .formLogin()
+        }
+    }
+
+    def "custom logout success url prevents rendering"() {
+        setup:
+            loadConfig(DefaultLoginPageCustomLogoutConfig)
+            springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when: "logout success"
+            request.requestURI = "/login"
+            request.queryString = "logout"
+            request.method = "GET"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "default success page is NOT rendered (application is in charge of it)"
+            response.getContentAsString() == ""
+    }
+
+    @Configuration
+    static class DefaultLoginPageCustomLogoutConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .logout()
+                    .logoutSuccessUrl("/login?logout")
+                    .and()
+                .formLogin()
+        }
+    }
+
+    def "http/form-login default login with remember me"() {
+        setup:
+            loadConfig(DefaultLoginPageWithRememberMeConfig)
+            springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when: "request the login page"
+            setup()
+            request.requestURI = "/login"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
+<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
+ <table>
+    <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
+    <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
+    <tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
+    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
+  </table>
+</form></body></html>"""
+    }
+
+    @Configuration
+    static class DefaultLoginPageWithRememberMeConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+                    .and()
+                .rememberMe()
+        }
+    }
+
+    def "http/form-login default login with openid"() {
+        setup:
+            loadConfig(DefaultLoginPageWithOpenIDConfig)
+            springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when: "request the login page"
+            request.requestURI = "/login"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getContentAsString() == """<html><head><title>Login Page</title></head><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
+ <table>
+    <tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
+    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
+  </table>
+</form></body></html>"""
+    }
+
+    @Configuration
+    static class DefaultLoginPageWithOpenIDConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .openidLogin()
+        }
+    }
+
+    def "http/form-login default login with openid, form login, and rememberme"() {
+        setup:
+            loadConfig(DefaultLoginPageWithFormLoginOpenIDRememberMeConfig)
+            springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when: "request the login page"
+            request.requestURI = "/login"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
+<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
+ <table>
+    <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
+    <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
+    <tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
+    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
+  </table>
+</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
+ <table>
+    <tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
+    <tr><td><input type='checkbox' name='remember-me'></td><td>Remember me on this computer.</td></tr>
+    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
+  </table>
+</form></body></html>"""
+    }
+
+    @Configuration
+    static class DefaultLoginPageWithFormLoginOpenIDRememberMeConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .rememberMe()
+                    .and()
+                .formLogin()
+                    .and()
+                .openidLogin()
+        }
+    }
+
+    def "default login with custom AuthenticationEntryPoint"() {
+        when:
+            loadConfig(DefaultLoginWithCustomAuthenticationEntryPointConfig)
+        then:
+            !findFilter(DefaultLoginPageViewFilter)
+    }
+
+    @Configuration
+    static class DefaultLoginWithCustomAuthenticationEntryPointConfig extends BaseWebConfig {
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .exceptionHandling()
+                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+        }
+    }
+
+    def "DefaultLoginPage ObjectPostProcessor"() {
+        setup:
+            AnyObjectPostProcessor objectPostProcessor = Mock()
+        when:
+            HttpSecurity http = new HttpSecurity(objectPostProcessor, authenticationBldr, [:])
+            DefaultLoginPageConfigurer defaultLoginConfig = new DefaultLoginPageConfigurer([builder:http])
+            defaultLoginConfig.addObjectPostProcessor(objectPostProcessor)
+            http
+                // must set builder manually due to groovy not selecting correct method
+                .apply(defaultLoginConfig).and()
+                .formLogin()
+                    .and()
+                .build()
+
+        then: "DefaultLoginPageGeneratingFilter is registered with LifecycleManager"
+            1 * objectPostProcessor.postProcess(_ as DefaultLoginPageViewFilter) >> {DefaultLoginPageViewFilter o -> o}
+            1 * objectPostProcessor.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o}
+            1 * objectPostProcessor.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o}
+    }
+}

+ 43 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy

@@ -0,0 +1,43 @@
+/*
+ * 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.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.web.access.ExceptionTranslationFilter
+
+/**
+ *
+ * @author Rob Winch
+ */
+class ExceptionHandlingConfigurerTests extends BaseSpringSpec {
+
+    def "exception ObjectPostProcessor"() {
+        setup: "initialize the AUTH_FILTER as a mock"
+            AnyObjectPostProcessor opp = Mock()
+        when:
+            HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
+            http
+                .exceptionHandling()
+                    .and()
+                .build()
+
+        then: "ExceptionTranslationFilter is registered with LifecycleManager"
+            1 * opp.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o}
+    }
+}

+ 413 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy

@@ -0,0 +1,413 @@
+/*
+ * 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.beans.factory.BeanCreationException;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.vote.AffirmativeBased;
+import org.springframework.security.authentication.RememberMeAuthenticationToken
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.SecurityExpressions.*
+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.ExpressionUrlAuthorizationConfigurer;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+
+public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec {
+
+    def "hasAnyAuthority('ROLE_USER')"() {
+        when:
+            def expression = ExpressionUrlAuthorizationConfigurer.hasAnyAuthority("ROLE_USER")
+        then:
+            expression == "hasAnyAuthority('ROLE_USER')"
+    }
+
+    def "hasAnyAuthority('ROLE_USER','ROLE_ADMIN')"() {
+        when:
+            def expression = ExpressionUrlAuthorizationConfigurer.hasAnyAuthority("ROLE_USER","ROLE_ADMIN")
+        then:
+            expression == "hasAnyAuthority('ROLE_USER','ROLE_ADMIN')"
+    }
+
+    def "hasRole('ROLE_USER') is rejected due to starting with ROLE_"() {
+        when:
+            def expression = ExpressionUrlAuthorizationConfigurer.hasRole("ROLE_USER")
+        then:
+            IllegalArgumentException e = thrown()
+            e.message == "role should not start with 'ROLE_' since it is automatically inserted. Got 'ROLE_USER'"
+    }
+
+    def "authorizeUrls() uses AffirmativeBased AccessDecisionManager"() {
+        when: "Load Config with no specific AccessDecisionManager"
+            loadConfig(NoSpecificAccessDecessionManagerConfig)
+        then: "AccessDecessionManager matches the HttpSecurityBuilder's default"
+            findFilter(FilterSecurityInterceptor).accessDecisionManager.class == AffirmativeBased
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class NoSpecificAccessDecessionManagerConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+        }
+    }
+
+    def "authorizeUrls() no requests"() {
+        when: "Load Config with no requests"
+            loadConfig(NoRequestsConfig)
+        then: "A meaningful exception is thrown"
+            BeanCreationException success = thrown()
+            success.message.contains "At least one mapping is required (i.e. authorizeUrls().anyRequest.authenticated())"
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class NoRequestsConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+        }
+    }
+
+    def "authorizeUrls() incomplete mapping"() {
+        when: "Load Config with incomplete mapping"
+            loadConfig(IncompleteMappingConfig)
+        then: "A meaningful exception is thrown"
+            BeanCreationException success = thrown()
+            success.message.contains "An incomplete mapping was found for "
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class IncompleteMappingConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .authorizeUrls()
+                    .antMatchers("/a").authenticated()
+                    .anyRequest()
+        }
+    }
+
+    def "authorizeUrls() hasAuthority"() {
+        setup:
+            loadConfig(HasAuthorityConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+        when:
+            super.setup()
+            login()
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+        when:
+            super.setup()
+            login("user","ROLE_INVALID")
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class HasAuthorityConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().hasAuthority("ROLE_USER")
+        }
+    }
+
+    def "authorizeUrls() hasAnyAuthority"() {
+        setup:
+            loadConfig(HasAnyAuthorityConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+        when:
+            super.setup()
+            login("user","ROLE_ADMIN")
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+        when:
+            super.setup()
+            login("user","ROLE_DBA")
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+        when:
+            super.setup()
+            login("user","ROLE_INVALID")
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class HasAnyAuthorityConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().hasAnyAuthority("ROLE_ADMIN","ROLE_DBA")
+        }
+    }
+
+    def "authorizeUrls() hasIpAddress"() {
+        setup:
+            loadConfig(HasIpAddressConfig)
+        when:
+            request.remoteAddr = "192.168.1.1"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+        when:
+            super.setup()
+            request.remoteAddr = "192.168.1.0"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class HasIpAddressConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().hasIpAddress("192.168.1.0")
+        }
+    }
+
+    def "authorizeUrls() anonymous"() {
+        setup:
+            loadConfig(AnonymousConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+        when:
+            super.setup()
+            login()
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class AnonymousConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().anonymous()
+        }
+    }
+
+    def "authorizeUrls() rememberMe"() {
+        setup:
+            loadConfig(RememberMeConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+        when:
+            super.setup()
+            login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class RememberMeConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .rememberMe()
+                    .and()
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().rememberMe()
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER")
+        }
+    }
+
+    def "authorizeUrls() denyAll"() {
+        setup:
+            loadConfig(DenyAllConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+        when:
+            super.setup()
+            login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class DenyAllConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().denyAll()
+        }
+    }
+
+    def "authorizeUrls() not denyAll"() {
+        setup:
+            loadConfig(NotDenyAllConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+        when:
+            super.setup()
+            login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class NotDenyAllConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().not().denyAll()
+        }
+    }
+
+    def "authorizeUrls() fullyAuthenticated"() {
+        setup:
+            loadConfig(FullyAuthenticatedConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+        when:
+            super.setup()
+            login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 403
+        when:
+            super.setup()
+            login()
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then:
+            response.status == 200
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class FullyAuthenticatedConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .rememberMe()
+                    .and()
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().fullyAuthenticated()
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER")
+        }
+    }
+
+    def "authorizeUrls() access"() {
+        setup:
+            loadConfig(AccessConfig)
+        when:
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "Access is granted due to GET"
+            response.status == 200
+        when:
+            super.setup()
+            login()
+            request.method = "POST"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "Access is granted due to role"
+            response.status == 200
+        when:
+            super.setup()
+            request.method = "POST"
+            springSecurityFilterChain.doFilter(request,response,chain)
+        then: "Access is denied"
+            response.status == 403
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class AccessConfig extends WebSecurityConfigurerAdapter {
+        protected void configure(HttpSecurity http) throws Exception {
+            http
+                .rememberMe()
+                    .and()
+                .httpBasic()
+                    .and()
+                .authorizeUrls()
+                    .anyRequest().access("hasRole('ROLE_USER') or request.method == 'GET'")
+        }
+
+        @Override
+        protected void registerAuthentication(AuthenticationManagerBuilder auth)
+                throws Exception {
+            auth
+                .inMemoryAuthentication()
+                    .withUser("user").password("password").roles("USER")
+        }
+    }
+}

+ 214 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy

@@ -0,0 +1,214 @@
+/*
+ * 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.AnnotationConfigApplicationContext
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockFilterChain
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.security.authentication.BadCredentialsException
+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.builders.WebSecurity
+import org.springframework.security.config.annotation.web.configuration.BaseWebConfig
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.web.AuthenticationEntryPoint
+import org.springframework.security.web.FilterChainProxy
+import org.springframework.security.web.PortMapper
+import org.springframework.security.web.access.ExceptionTranslationFilter
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
+import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+import org.springframework.security.web.authentication.logout.LogoutFilter
+import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy
+import org.springframework.security.web.context.SecurityContextPersistenceFilter
+import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
+import org.springframework.security.web.session.SessionManagementFilter
+import org.springframework.security.web.util.AnyRequestMatcher
+import org.springframework.test.util.ReflectionTestUtils
+
+/**
+ *
+ * @author Rob Winch
+ */
+class FormLoginConfigurerTests extends BaseSpringSpec {
+    def "Form Login"() {
+        when: "load formLogin()"
+            context = new AnnotationConfigApplicationContext(FormLoginConfig)
+
+        then: "FilterChains configured correctly"
+            def filterChains = filterChains()
+            filterChains.size() == 2
+            filterChains[0].requestMatcher.pattern == '/resources/**'
+            filterChains[0].filters.empty
+            filterChains[1].requestMatcher instanceof AnyRequestMatcher
+            filterChains[1].filters.collect { it.class.name.contains('$') ? it.class.superclass : it.class } ==
+                    [SecurityContextPersistenceFilter, LogoutFilter, UsernamePasswordAuthenticationFilter,
+                     RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter,
+                     AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ]
+
+        and: "UsernamePasswordAuthentictionFilter is configured correctly"
+            UsernamePasswordAuthenticationFilter authFilter = findFilter(UsernamePasswordAuthenticationFilter,1)
+            authFilter.usernameParameter == "username"
+            authFilter.passwordParameter == "password"
+            authFilter.failureHandler.defaultFailureUrl == "/login?error"
+            authFilter.successHandler.defaultTargetUrl == "/"
+            authFilter.requiresAuthentication(new MockHttpServletRequest(requestURI : "/login", method: "POST"), new MockHttpServletResponse())
+            !authFilter.requiresAuthentication(new MockHttpServletRequest(requestURI : "/login", method: "GET"), new MockHttpServletResponse())
+
+        and: "SessionFixationProtectionStrategy is configured correctly"
+            SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy")
+            sessionStrategy.migrateSessionAttributes
+
+        and: "Exception handling is configured correctly"
+            AuthenticationEntryPoint authEntryPoint = filterChains[1].filters.find { it instanceof ExceptionTranslationFilter}.authenticationEntryPoint
+            MockHttpServletResponse response = new MockHttpServletResponse()
+            authEntryPoint.commence(new MockHttpServletRequest(requestURI: "/private/"), response, new BadCredentialsException(""))
+            response.redirectedUrl == "http://localhost/login"
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class FormLoginConfig extends BaseWebConfig {
+        @Override
+        public void configure(WebSecurity web)	throws Exception {
+            web
+                .ignoring()
+                    .antMatchers("/resources/**");
+        }
+
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+                    .loginUrl("/login")
+        }
+    }
+
+    def "FormLogin.permitAll()"() {
+        when: "load formLogin() with permitAll"
+            context = new AnnotationConfigApplicationContext(FormLoginConfigPermitAll)
+
+        then: "the formLogin URLs are granted access"
+            FilterChainProxy filterChain = context.getBean(FilterChainProxy)
+            MockHttpServletResponse response = new MockHttpServletResponse()
+            filterChain.doFilter(new MockHttpServletRequest(servletPath : servletPath, requestURI: servletPath, queryString: query, method: method), response, new MockFilterChain())
+            response.redirectedUrl == redirectUrl
+
+        where:
+            servletPath | method | query | redirectUrl
+            "/login" | "GET" | null | null
+            "/login" | "POST" | null | "/login?error"
+            "/login" | "GET" | "error" | null
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class FormLoginConfigPermitAll extends BaseWebConfig {
+
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+                    .permitAll()
+        }
+    }
+
+    def "FormLogin uses PortMapper"() {
+        when: "load formLogin() with permitAll"
+            FormLoginUsesPortMapperConfig.PORT_MAPPER = Mock(PortMapper)
+            loadConfig(FormLoginUsesPortMapperConfig)
+        then: "the formLogin URLs are granted access"
+            findFilter(ExceptionTranslationFilter).authenticationEntryPoint.portMapper == FormLoginUsesPortMapperConfig.PORT_MAPPER
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    static class FormLoginUsesPortMapperConfig extends BaseWebConfig {
+        static PortMapper PORT_MAPPER
+
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+                    .permitAll()
+                    .and()
+                .portMapper()
+                    .portMapper(PORT_MAPPER)
+        }
+    }
+
+    def "FormLogin permitAll ignores failureUrl when failureHandler set"() {
+        setup:
+            PermitAllIgnoresFailureHandlerConfig.FAILURE_HANDLER = Mock(AuthenticationFailureHandler)
+            loadConfig(PermitAllIgnoresFailureHandlerConfig)
+            FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy)
+        when: "access default failureUrl and configured explicit FailureHandler"
+            MockHttpServletRequest request = new MockHttpServletRequest(requestURI:"/login",queryString:"error")
+            MockHttpServletResponse response = new MockHttpServletResponse()
+            springSecurityFilterChain.doFilter(request,response,new MockFilterChain())
+        then: "access is not granted to the failure handler (sent to login page)"
+            response.status == 302
+    }
+
+    @EnableWebSecurity
+    @Configuration
+    static class PermitAllIgnoresFailureHandlerConfig extends BaseWebConfig {
+        static AuthenticationFailureHandler FAILURE_HANDLER
+
+        @Override
+        protected void configure(HttpSecurity http) {
+            http
+                .authorizeUrls()
+                    .anyRequest().hasRole("USER")
+                    .and()
+                .formLogin()
+                    .failureHandler(FAILURE_HANDLER)
+                    .permitAll()
+        }
+    }
+
+    def "formLogin ObjectPostProcessor"() {
+        setup: "initialize the AUTH_FILTER as a mock"
+            AnyObjectPostProcessor opp = Mock()
+            HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
+        when:
+            http
+                .formLogin()
+                    .and()
+                .build()
+
+        then: "UsernamePasswordAuthenticationFilter is registered with LifecycleManager"
+            1 * opp.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o}
+        and: "LoginUrlAuthenticationEntryPoint is registered with LifecycleManager"
+            1 * opp.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o}
+    }
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov