ソースを参照

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 年 前
コミット
29632ee9ea

+ 9 - 3
config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java

@@ -56,6 +56,8 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
     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>();
 
     private final boolean allowConfigurersOfSameType;
@@ -122,9 +124,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;
     }
 
@@ -198,7 +200,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);
             }
         }
     }
@@ -363,7 +365,11 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
     private void init() throws Exception {
         Collection<SecurityConfigurer<O,B>> configurers = getConfigurers();
 
-        for(SecurityConfigurer<O,B> configurer : configurers ) {
+        for (SecurityConfigurer<O, B> configurer : configurers) {
+            configurer.init((B) this);
+        }
+
+        for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
             configurer.init((B) this);
         }
     }

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