Browse Source

Add Support for Custom Default Configuration in Web Security

Fixes gh-4102
Rob Winch 8 years ago
parent
commit
94e580fe64

+ 27 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java

@@ -34,6 +34,7 @@ import org.springframework.beans.factory.BeanFactoryUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.annotation.Order;
+import org.springframework.core.io.support.SpringFactoriesLoader;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
@@ -44,6 +45,7 @@ import org.springframework.security.config.annotation.authentication.configurati
 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.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
 import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer;
 import org.springframework.security.core.Authentication;
@@ -59,8 +61,23 @@ import org.springframework.web.accept.ContentNegotiationStrategy;
 import org.springframework.web.accept.HeaderContentNegotiationStrategy;
 
 /**
- * Provides a convenient base class for creating a {@link WebSecurityConfigurer} instance.
- * The implementation allows customization by overriding methods.
+ * Provides a convenient base class for creating a {@link WebSecurityConfigurer}
+ * instance. The implementation allows customization by overriding methods.
+ *
+ * <p>
+ * Will automatically apply the result of looking up
+ * {@link AbstractHttpConfigurer} from {@link SpringFactoriesLoader} to allow
+ * developers to extend the defaults.
+ * To do this, you must create a class that extends AbstractHttpConfigurer and then create a file in the classpath at "META-INF/spring.factories" that looks something like:
+ * </p>
+ * <pre>
+ * org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer
+ * </pre>
+ * If you have multiple classes that should be added you can use "," to separate the values. For example:
+ *
+ * <pre>
+ * org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer, sample.OtherThatExtendsAbstractHttpConfigurer
+ * </pre>
  *
  * @see EnableWebSecurity
  *
@@ -165,6 +182,7 @@ public abstract class WebSecurityConfigurerAdapter implements
 	 * ] * @return the {@link HttpSecurity}
 	 * @throws Exception
 	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
 	protected final HttpSecurity getHttp() throws Exception {
 		if (http != null) {
 			return http;
@@ -195,6 +213,13 @@ public abstract class WebSecurityConfigurerAdapter implements
 				.apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
 				.logout();
 			// @formatter:on
+			ClassLoader classLoader = this.context.getClassLoader();
+			List<AbstractHttpConfigurer> defaultHttpConfigurers =
+					SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
+
+			for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
+				http.apply(configurer);
+			}
 		}
 		configure(http);
 		return http;

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

@@ -29,7 +29,7 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
  * @author Rob Winch
  *
  */
-abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
+public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
 		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
 
 	/**

+ 96 - 0
config/src/test/java/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterPowermockTests.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2016 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.assertj.core.api.Assertions.assertThat;
+import static org.powermock.api.mockito.PowerMockito.*;
+
+import java.util.Arrays;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.springframework.core.io.support.SpringFactoriesLoader;
+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.AbstractHttpConfigurer;
+import org.springframework.web.context.ConfigurableWebApplicationContext;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ SpringFactoriesLoader.class })
+public class WebSecurityConfigurerAdapterPowermockTests {
+	ConfigurableWebApplicationContext context;
+
+	@After
+	public void close() {
+		if(context != null) {
+			context.close();
+		}
+	}
+
+	@Test
+	public void loadConfigWhenDefaultConfigurerAsSpringFactoryhenDefaultConfigurerApplied() {
+		spy(SpringFactoriesLoader.class);
+		DefaultConfigurer configurer = new DefaultConfigurer();
+		when(SpringFactoriesLoader
+				.loadFactories(AbstractHttpConfigurer.class, getClass().getClassLoader()))
+			.thenReturn(Arrays.<AbstractHttpConfigurer>asList(configurer));
+
+		loadConfig(Config.class);
+
+		assertThat(configurer.init).isTrue();
+		assertThat(configurer.configure).isTrue();
+	}
+
+	private void loadConfig(Class<?>... classes) {
+		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+		context.setClassLoader(getClass().getClassLoader());
+		context.register(classes);
+		context.refresh();
+		this.context = context;
+	}
+
+	@EnableWebSecurity
+	static class Config extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+		}
+	}
+
+	static class DefaultConfigurer extends AbstractHttpConfigurer<DefaultConfigurer,HttpSecurity> {
+		boolean init;
+		boolean configure;
+
+		@Override
+		public void init(HttpSecurity builder) throws Exception {
+			this.init = true;
+		}
+
+		@Override
+		public void configure(HttpSecurity builder) throws Exception {
+			this.configure = true;
+		}
+	}
+}

+ 87 - 0
docs/manual/src/docs/asciidoc/index.adoc

@@ -1101,6 +1101,93 @@ protected void configure(HttpSecurity http) throws Exception {
 }
 ----
 
+[[jc-custom-dsls]]
+=== Custom DSLs
+
+You can provide your own custom DSLs in Spring Security.
+For example, you might have something that looks like this:
+
+[source,java]
+----
+public class MyCustomDsl extends AbstractHttpConfigurer<CorsConfigurerMyCustomDsl, HttpSecurity> {
+	private boolean flag;
+
+	@Override
+	public void init(H http) throws Exception {
+		// any method that adds another configurer
+		// must be done in the init method
+		http.csrf().disable();
+	}
+
+	@Override
+	public void configure(H http) throws Exception {
+		ApplicationContext context = http.getSharedObject(ApplicationContext.class);
+
+		// here we lookup from the ApplicationContext. You can also just create a new instance.
+		MyFilter myFilter = context.getBean(MyFilter.class);
+		myFilter.setFlag(flag);
+		http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
+	}
+
+	public MyCustomDsl flag(boolean value) {
+		this.flag = value;
+		return this;
+	}
+
+	public static MyCustomDsl customDsl() {
+		return new MyCustomDsl();
+	}
+}
+----
+
+NOTE: This is actually how methods like `HttpSecurity.authorizeRequests()` are implemented.
+
+The custom DSL can then be used like this:
+
+[source,java]
+----
+@EnableWebSecurity
+public class Config extends WebSecurityConfigurerAdapter {
+	@Override
+	protected void configure(HttpSecurity http) throws Exception {
+		http
+			.apply(customDsl())
+				.flag(true)
+				.and()
+			...;
+	}
+}
+----
+
+The code is invoked in the following order:
+
+* Code in `Config`s configure method is invoked
+* Code in `MyCustomDsl`s init method is invoked
+* Code in `MyCustomDsl`s configure method is invoked
+
+If you want, you can have `WebSecurityConfiguerAdapter` add `MyCustomDsl` by default by using `SpringFactories`.
+For example, you would create a resource on the classpath named `META-INF/spring.factories` with the following contents:
+
+.META-INF/spring.factories
+----
+org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
+----
+
+Users wishing to disable the default can do so explicitly.
+
+[source,java]
+----
+@EnableWebSecurity
+public class Config extends WebSecurityConfigurerAdapter {
+	@Override
+	protected void configure(HttpSecurity http) throws Exception {
+		http
+			.apply(customDsl()).disable()
+			...;
+	}
+}
+----
+
 [[ns-config]]
 == Security Namespace Configuration