Add Support for Custom Default Configuration in Web Security

Fixes gh-4102
This commit is contained in:
Rob Winch 2016-10-19 16:15:56 -05:00
parent af9139b613
commit 94e580fe64
4 changed files with 211 additions and 3 deletions

View File

@ -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;

View File

@ -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> {
/**

View File

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

View File

@ -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