Skip to content

Commit 4e2a050

Browse files
committed
Customizer for WebSecurity
Closes gh-8978
1 parent bf067d6 commit 4e2a050

File tree

4 files changed

+214
-2
lines changed

4 files changed

+214
-2
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4242
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
4343
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
44+
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
4445
import org.springframework.security.core.context.SecurityContext;
4546
import org.springframework.security.web.DefaultSecurityFilterChain;
4647
import org.springframework.security.web.FilterChainProxy;
@@ -69,8 +70,8 @@
6970
*
7071
* <p>
7172
* Customizations to the {@link WebSecurity} can be made by creating a
72-
* {@link WebSecurityConfigurer} or more likely by overriding
73-
* {@link WebSecurityConfigurerAdapter}.
73+
* {@link WebSecurityConfigurer}, overriding {@link WebSecurityConfigurerAdapter} or
74+
* exposing a {@link WebSecurityCustomizer} bean.
7475
* </p>
7576
*
7677
* @author Rob Winch

config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
7777

7878
private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
7979

80+
private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();
81+
8082
private ClassLoader beanClassLoader;
8183

8284
@Autowired(required = false)
@@ -119,6 +121,9 @@ public Filter springSecurityFilterChain() throws Exception {
119121
}
120122
}
121123
}
124+
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
125+
customizer.customize(this.webSecurity);
126+
}
122127
return this.webSecurity.build();
123128
}
124129

@@ -175,6 +180,12 @@ void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
175180
this.securityFilterChains = securityFilterChains;
176181
}
177182

183+
@Autowired(required = false)
184+
void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
185+
webSecurityCustomizers.sort(AnnotationAwareOrderComparator.INSTANCE);
186+
this.webSecurityCustomizers = webSecurityCustomizers;
187+
}
188+
178189
@Bean
179190
public static BeanFactoryPostProcessor conversionServicePostProcessor() {
180191
return new RsaKeyConversionServicePostProcessor();
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.web.configuration;
18+
19+
import org.springframework.security.config.annotation.web.builders.WebSecurity;
20+
21+
/**
22+
* Callback interface for customizing {@link WebSecurity}.
23+
*
24+
* Beans of this type will automatically be used by {@link WebSecurityConfiguration} to
25+
* customize {@link WebSecurity}.
26+
*
27+
* Example usage:
28+
*
29+
* <pre>
30+
* &#064;Bean
31+
* public WebSecurityCustomizer ignoringCustomizer() {
32+
* return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
33+
* }
34+
* </pre>
35+
*
36+
* @author Eleftheria Stein
37+
* @since 5.4
38+
*/
39+
@FunctionalInterface
40+
public interface WebSecurityCustomizer {
41+
42+
/**
43+
* Performs the customizations on {@link WebSecurity}.
44+
* @param web the instance of {@link WebSecurity} to apply to customizations to
45+
*/
46+
void customize(WebSecurity web);
47+
48+
}

config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,76 @@ public void loadConfigWhenBothAdapterAndFilterChainConfiguredThenException() {
256256

257257
}
258258

259+
@Test
260+
public void loadConfigWhenOnlyWebSecurityCustomizerThenDefaultFilterChainCreated() {
261+
this.spring.register(WebSecurityCustomizerConfig.class).autowire();
262+
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
263+
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
264+
assertThat(filterChains).hasSize(3);
265+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
266+
request.setServletPath("/ignore1");
267+
assertThat(filterChains.get(0).matches(request)).isTrue();
268+
assertThat(filterChains.get(0).getFilters()).isEmpty();
269+
request.setServletPath("/ignore2");
270+
assertThat(filterChains.get(1).matches(request)).isTrue();
271+
assertThat(filterChains.get(1).getFilters()).isEmpty();
272+
request.setServletPath("/test/**");
273+
assertThat(filterChains.get(2).matches(request)).isTrue();
274+
}
275+
276+
@Test
277+
public void loadConfigWhenWebSecurityCustomizerAndFilterChainThenFilterChainsOrdered() {
278+
this.spring.register(CustomizerAndFilterChainConfig.class).autowire();
279+
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
280+
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
281+
assertThat(filterChains).hasSize(3);
282+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
283+
request.setServletPath("/ignore1");
284+
assertThat(filterChains.get(0).matches(request)).isTrue();
285+
assertThat(filterChains.get(0).getFilters()).isEmpty();
286+
request.setServletPath("/ignore2");
287+
assertThat(filterChains.get(1).matches(request)).isTrue();
288+
assertThat(filterChains.get(1).getFilters()).isEmpty();
289+
request.setServletPath("/role1/**");
290+
assertThat(filterChains.get(2).matches(request)).isTrue();
291+
request.setServletPath("/test/**");
292+
assertThat(filterChains.get(2).matches(request)).isFalse();
293+
}
294+
295+
@Test
296+
public void loadConfigWhenWebSecurityCustomizerAndWebSecurityConfigurerAdapterThenFilterChainsOrdered() {
297+
this.spring.register(CustomizerAndAdapterConfig.class).autowire();
298+
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
299+
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
300+
assertThat(filterChains).hasSize(3);
301+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
302+
request.setServletPath("/ignore1");
303+
assertThat(filterChains.get(0).matches(request)).isTrue();
304+
assertThat(filterChains.get(0).getFilters()).isEmpty();
305+
request.setServletPath("/ignore2");
306+
assertThat(filterChains.get(1).matches(request)).isTrue();
307+
assertThat(filterChains.get(1).getFilters()).isEmpty();
308+
request.setServletPath("/role1/**");
309+
assertThat(filterChains.get(2).matches(request)).isTrue();
310+
request.setServletPath("/test/**");
311+
assertThat(filterChains.get(2).matches(request)).isFalse();
312+
}
313+
314+
@Test
315+
public void loadConfigWhenCustomizerAndAdapterConfigureWebSecurityThenBothConfigurationsApplied() {
316+
this.spring.register(CustomizerAndAdapterIgnoringConfig.class).autowire();
317+
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
318+
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
319+
assertThat(filterChains).hasSize(3);
320+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
321+
request.setServletPath("/ignore1");
322+
assertThat(filterChains.get(0).matches(request)).isTrue();
323+
assertThat(filterChains.get(0).getFilters()).isEmpty();
324+
request.setServletPath("/ignore2");
325+
assertThat(filterChains.get(1).matches(request)).isTrue();
326+
assertThat(filterChains.get(1).getFilters()).isEmpty();
327+
}
328+
259329
@EnableWebSecurity
260330
@Import(AuthenticationTestConfiguration.class)
261331
static class SortedWebSecurityConfigurerAdaptersConfig {
@@ -682,4 +752,86 @@ protected void configure(HttpSecurity http) throws Exception {
682752

683753
}
684754

755+
@EnableWebSecurity
756+
@Import(AuthenticationTestConfiguration.class)
757+
static class WebSecurityCustomizerConfig {
758+
759+
@Bean
760+
public WebSecurityCustomizer webSecurityCustomizer() {
761+
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
762+
}
763+
764+
}
765+
766+
@EnableWebSecurity
767+
@Import(AuthenticationTestConfiguration.class)
768+
static class CustomizerAndFilterChainConfig {
769+
770+
@Bean
771+
public WebSecurityCustomizer webSecurityCustomizer() {
772+
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
773+
}
774+
775+
@Bean
776+
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
777+
// @formatter:off
778+
return http
779+
.antMatcher("/role1/**")
780+
.authorizeRequests((authorize) -> authorize
781+
.anyRequest().hasRole("1")
782+
)
783+
.build();
784+
// @formatter:on
785+
}
786+
787+
}
788+
789+
@EnableWebSecurity
790+
@Import(AuthenticationTestConfiguration.class)
791+
static class CustomizerAndAdapterConfig {
792+
793+
@Bean
794+
public WebSecurityCustomizer webSecurityCustomizer() {
795+
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
796+
}
797+
798+
@Configuration
799+
static class SecurityConfig extends WebSecurityConfigurerAdapter {
800+
801+
@Override
802+
protected void configure(HttpSecurity http) throws Exception {
803+
// @formatter:off
804+
http
805+
.antMatcher("/role1/**")
806+
.authorizeRequests((authorize) -> authorize
807+
.anyRequest().hasRole("1")
808+
);
809+
// @formatter:on
810+
}
811+
812+
}
813+
814+
}
815+
816+
@EnableWebSecurity
817+
@Import(AuthenticationTestConfiguration.class)
818+
static class CustomizerAndAdapterIgnoringConfig {
819+
820+
@Bean
821+
public WebSecurityCustomizer webSecurityCustomizer() {
822+
return (web) -> web.ignoring().antMatchers("/ignore1");
823+
}
824+
825+
@Configuration
826+
static class SecurityConfig extends WebSecurityConfigurerAdapter {
827+
828+
@Override
829+
public void configure(WebSecurity web) throws Exception {
830+
web.ignoring().antMatchers("/ignore2");
831+
}
832+
833+
}
834+
835+
}
836+
685837
}

0 commit comments

Comments
 (0)