diff --git a/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java b/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java index 2102c74701..67579408c1 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java +++ b/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java @@ -56,6 +56,8 @@ public abstract class AbstractConfiguredSecurityBuilder>, List>> configurers = new LinkedHashMap>, List>>(); + private final List> configurersAddedInInitializing = new ArrayList>(); + private final Map,Object> sharedObjects = new HashMap,Object>(); private final boolean allowConfigurersOfSameType; @@ -122,9 +124,9 @@ public abstract class AbstractConfiguredSecurityBuilder> C apply(C configurer) throws Exception { - add(configurer); configurer.addObjectPostProcessor(objectPostProcessor); configurer.setBuilder((B) this); + add(configurer); return configurer; } @@ -198,7 +200,7 @@ public abstract class AbstractConfiguredSecurityBuilder> configurers = getConfigurers(); - for(SecurityConfigurer configurer : configurers ) { + for (SecurityConfigurer configurer : configurers) { + configurer.init((B) this); + } + + for (SecurityConfigurer configurer : configurersAddedInInitializing) { configurer.init((B) this); } } diff --git a/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomConfigurer.java b/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomConfigurer.java new file mode 100644 index 0000000000..50fbdb60a3 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomConfigurer.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.config.http.customconfigurer; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.web.DefaultSecurityFilterChain; + +/** + * @author Rob Winch + * + */ +public class CustomConfigurer extends SecurityConfigurerAdapter { + + @Value("${permitAllPattern}") + private String permitAllPattern; + + private String loginPage = "/login"; + + /* (non-Javadoc) + * @see org.springframework.security.config.annotation.SecurityConfigurerAdapter#init(org.springframework.security.config.annotation.SecurityBuilder) + */ + @SuppressWarnings("unchecked") + @Override + public void init(HttpSecurity http) throws Exception { + // autowire this bean + ApplicationContext context = http.getSharedObject(ApplicationContext.class); + context.getAutowireCapableBeanFactory().autowireBean(this); + + http + .authorizeRequests() + .antMatchers(permitAllPattern).permitAll() + .anyRequest().authenticated(); + + if(http.getConfigurer(FormLoginConfigurer.class) == null) { + // only apply if formLogin() was not invoked by the user + http + .formLogin() + .loginPage(loginPage); + } + } + + public CustomConfigurer loginPage(String loginPage) { + this.loginPage = loginPage; + return this; + } + + public static CustomConfigurer customConfigurer() { + return new CustomConfigurer(); + } +} \ No newline at end of file diff --git a/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java b/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java new file mode 100644 index 0000000000..2af303d9d5 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java @@ -0,0 +1,164 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.config.http.customconfigurer; + +import static org.fest.assertions.Assertions.assertThat; +import static org.springframework.security.config.http.customconfigurer.CustomConfigurer.customConfigurer; + +import java.util.Properties; + +import javax.servlet.http.HttpServletResponse; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.FilterChainProxy; + +/** + * @author Rob Winch + * + */ +public class CustomHttpSecurityConfigurerTests { + @Autowired + ConfigurableApplicationContext context; + + @Autowired + FilterChainProxy springSecurityFilterChain; + + MockHttpServletRequest request; + MockHttpServletResponse response; + MockFilterChain chain; + + @Before + public void setup() { + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + chain = new MockFilterChain(); + request.setMethod("GET"); + } + + @After + public void cleanup() { + if(context != null) { + context.close(); + } + } + + @Test + public void customConfiguerPermitAll() throws Exception { + loadContext(Config.class); + + request.setPathInfo("/public/something"); + + springSecurityFilterChain.doFilter(request, response, chain); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + } + + @Test + public void customConfiguerFormLogin() throws Exception { + loadContext(Config.class); + request.setPathInfo("/requires-authentication"); + + springSecurityFilterChain.doFilter(request, response, chain); + + assertThat(response.getRedirectedUrl()).endsWith("/custom"); + } + + @Test + public void customConfiguerCustomizeDisablesCsrf() throws Exception { + loadContext(ConfigCustomize.class); + request.setPathInfo("/public/something"); + request.setMethod("POST"); + + springSecurityFilterChain.doFilter(request, response, chain); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + } + + @Test + public void customConfiguerCustomizeFormLogin() throws Exception { + loadContext(ConfigCustomize.class); + request.setPathInfo("/requires-authentication"); + + springSecurityFilterChain.doFilter(request, response, chain); + + assertThat(response.getRedirectedUrl()).endsWith("/other"); + } + + private void loadContext(Class clazz) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(clazz); + context.getAutowireCapableBeanFactory().autowireBean(this); + } + + @EnableWebSecurity + static class Config extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .apply(customConfigurer()) + .loginPage("/custom"); + } + + @Bean + public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() { + // Typically externalize this as a properties file + Properties properties = new Properties(); + properties.setProperty("permitAllPattern", "/public/**"); + + PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); + propertyPlaceholderConfigurer.setProperties(properties); + return propertyPlaceholderConfigurer; + } + } + + @EnableWebSecurity + static class ConfigCustomize extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .apply(customConfigurer()) + .and() + .csrf().disable() + .formLogin() + .loginPage("/other"); + } + + @Bean + public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() { + // Typically externalize this as a properties file + Properties properties = new Properties(); + properties.setProperty("permitAllPattern", "/public/**"); + + PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); + propertyPlaceholderConfigurer.setProperties(properties); + return propertyPlaceholderConfigurer; + } + } +}