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