diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
index 1b2da75db7..9ca593f87b 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
+++ b/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.
+ *
+ *
+ * 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:
+ *
+ *
+ * org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer
+ *
+ * If you have multiple classes that should be added you can use "," to separate the values. For example:
+ *
+ *
+ * org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer, sample.OtherThatExtendsAbstractHttpConfigurer
+ *
*
* @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()).and()
.logout();
// @formatter:on
+ ClassLoader classLoader = this.context.getClassLoader();
+ List defaultHttpConfigurers =
+ SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
+
+ for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
+ http.apply(configurer);
+ }
}
configure(http);
return http;
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java
index 127b79be8d..d4aa4eda8d 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java
+++ b/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, B extends HttpSecurityBuilder>
+public abstract class AbstractHttpConfigurer, B extends HttpSecurityBuilder>
extends SecurityConfigurerAdapter {
/**
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterPowermockTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterPowermockTests.java
new file mode 100644
index 0000000000..a3795b3b94
--- /dev/null
+++ b/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.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 {
+ 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;
+ }
+ }
+}
diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc
index 485a6519d8..98437a2c21 100644
--- a/docs/manual/src/docs/asciidoc/index.adoc
+++ b/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 {
+ 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