فهرست منبع

SEC-3132: securityBuilder cannot be null

If a custom SecurityConfiguererAdapter applies another
SecurityConfigurerAdapter it caused an error securityBuilder cannot be null.

This commit fixes this.
Rob Winch 9 سال پیش
والد
کامیت
b719e0fbcc

+ 7 - 2
config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java

@@ -55,6 +55,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 	private final Log logger = LogFactory.getLog(getClass());
 
 	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 List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<SecurityConfigurer<O, B>>();
 
 	private final Map<Class<Object>, Object> sharedObjects = new HashMap<Class<Object>, Object>();
 
@@ -126,9 +127,9 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 	@SuppressWarnings("unchecked")
 	public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
 			throws Exception {
-		add(configurer);
 		configurer.addObjectPostProcessor(objectPostProcessor);
 		configurer.setBuilder((B) this);
+		add(configurer);
 		return configurer;
 	}
 
@@ -202,7 +203,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 			configs.add(configurer);
 			this.configurers.put(clazz, configs);
 			if (buildState.isInitializing()) {
-				configurer.init((B) this);
+				this.configurersAddedInInitializing.add(configurer);
 			}
 		}
 	}
@@ -368,6 +369,10 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 		for (SecurityConfigurer<O, B> configurer : configurers) {
 			configurer.init((B) this);
 		}
+
+		for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
+			configurer.init((B) this);
+		}
 	}
 
 	@SuppressWarnings("unchecked")

+ 67 - 0
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<DefaultSecurityFilterChain,HttpSecurity> {
+
+	@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();
+	}
+}

+ 164 - 0
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;
+		}
+	}
+}