Просмотр исходного кода

Customizer for WebSecurity

Closes gh-8978
Eleftheria Stein 5 лет назад
Родитель
Сommit
4e2a050c14

+ 3 - 2
config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java

@@ -41,6 +41,7 @@ import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.web.DefaultSecurityFilterChain;
 import org.springframework.security.web.FilterChainProxy;
@@ -69,8 +70,8 @@ import org.springframework.web.filter.DelegatingFilterProxy;
  *
  * <p>
  * Customizations to the {@link WebSecurity} can be made by creating a
- * {@link WebSecurityConfigurer} or more likely by overriding
- * {@link WebSecurityConfigurerAdapter}.
+ * {@link WebSecurityConfigurer}, overriding {@link WebSecurityConfigurerAdapter} or
+ * exposing a {@link WebSecurityCustomizer} bean.
  * </p>
  *
  * @author Rob Winch

+ 11 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java

@@ -77,6 +77,8 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
 
 	private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
 
+	private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();
+
 	private ClassLoader beanClassLoader;
 
 	@Autowired(required = false)
@@ -119,6 +121,9 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
 				}
 			}
 		}
+		for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
+			customizer.customize(this.webSecurity);
+		}
 		return this.webSecurity.build();
 	}
 
@@ -175,6 +180,12 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
 		this.securityFilterChains = securityFilterChains;
 	}
 
+	@Autowired(required = false)
+	void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
+		webSecurityCustomizers.sort(AnnotationAwareOrderComparator.INSTANCE);
+		this.webSecurityCustomizers = webSecurityCustomizers;
+	}
+
 	@Bean
 	public static BeanFactoryPostProcessor conversionServicePostProcessor() {
 		return new RsaKeyConversionServicePostProcessor();

+ 48 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityCustomizer.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.configuration;
+
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+
+/**
+ * Callback interface for customizing {@link WebSecurity}.
+ *
+ * Beans of this type will automatically be used by {@link WebSecurityConfiguration} to
+ * customize {@link WebSecurity}.
+ *
+ * Example usage:
+ *
+ * <pre>
+ * &#064;Bean
+ * public WebSecurityCustomizer ignoringCustomizer() {
+ * 	return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
+ * }
+ * </pre>
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@FunctionalInterface
+public interface WebSecurityCustomizer {
+
+	/**
+	 * Performs the customizations on {@link WebSecurity}.
+	 * @param web the instance of {@link WebSecurity} to apply to customizations to
+	 */
+	void customize(WebSecurity web);
+
+}

+ 152 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java

@@ -256,6 +256,76 @@ public class WebSecurityConfigurationTests {
 
 	}
 
+	@Test
+	public void loadConfigWhenOnlyWebSecurityCustomizerThenDefaultFilterChainCreated() {
+		this.spring.register(WebSecurityCustomizerConfig.class).autowire();
+		FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
+		List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
+		assertThat(filterChains).hasSize(3);
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+		request.setServletPath("/ignore1");
+		assertThat(filterChains.get(0).matches(request)).isTrue();
+		assertThat(filterChains.get(0).getFilters()).isEmpty();
+		request.setServletPath("/ignore2");
+		assertThat(filterChains.get(1).matches(request)).isTrue();
+		assertThat(filterChains.get(1).getFilters()).isEmpty();
+		request.setServletPath("/test/**");
+		assertThat(filterChains.get(2).matches(request)).isTrue();
+	}
+
+	@Test
+	public void loadConfigWhenWebSecurityCustomizerAndFilterChainThenFilterChainsOrdered() {
+		this.spring.register(CustomizerAndFilterChainConfig.class).autowire();
+		FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
+		List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
+		assertThat(filterChains).hasSize(3);
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+		request.setServletPath("/ignore1");
+		assertThat(filterChains.get(0).matches(request)).isTrue();
+		assertThat(filterChains.get(0).getFilters()).isEmpty();
+		request.setServletPath("/ignore2");
+		assertThat(filterChains.get(1).matches(request)).isTrue();
+		assertThat(filterChains.get(1).getFilters()).isEmpty();
+		request.setServletPath("/role1/**");
+		assertThat(filterChains.get(2).matches(request)).isTrue();
+		request.setServletPath("/test/**");
+		assertThat(filterChains.get(2).matches(request)).isFalse();
+	}
+
+	@Test
+	public void loadConfigWhenWebSecurityCustomizerAndWebSecurityConfigurerAdapterThenFilterChainsOrdered() {
+		this.spring.register(CustomizerAndAdapterConfig.class).autowire();
+		FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
+		List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
+		assertThat(filterChains).hasSize(3);
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+		request.setServletPath("/ignore1");
+		assertThat(filterChains.get(0).matches(request)).isTrue();
+		assertThat(filterChains.get(0).getFilters()).isEmpty();
+		request.setServletPath("/ignore2");
+		assertThat(filterChains.get(1).matches(request)).isTrue();
+		assertThat(filterChains.get(1).getFilters()).isEmpty();
+		request.setServletPath("/role1/**");
+		assertThat(filterChains.get(2).matches(request)).isTrue();
+		request.setServletPath("/test/**");
+		assertThat(filterChains.get(2).matches(request)).isFalse();
+	}
+
+	@Test
+	public void loadConfigWhenCustomizerAndAdapterConfigureWebSecurityThenBothConfigurationsApplied() {
+		this.spring.register(CustomizerAndAdapterIgnoringConfig.class).autowire();
+		FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
+		List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
+		assertThat(filterChains).hasSize(3);
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+		request.setServletPath("/ignore1");
+		assertThat(filterChains.get(0).matches(request)).isTrue();
+		assertThat(filterChains.get(0).getFilters()).isEmpty();
+		request.setServletPath("/ignore2");
+		assertThat(filterChains.get(1).matches(request)).isTrue();
+		assertThat(filterChains.get(1).getFilters()).isEmpty();
+	}
+
 	@EnableWebSecurity
 	@Import(AuthenticationTestConfiguration.class)
 	static class SortedWebSecurityConfigurerAdaptersConfig {
@@ -682,4 +752,86 @@ public class WebSecurityConfigurationTests {
 
 	}
 
+	@EnableWebSecurity
+	@Import(AuthenticationTestConfiguration.class)
+	static class WebSecurityCustomizerConfig {
+
+		@Bean
+		public WebSecurityCustomizer webSecurityCustomizer() {
+			return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Import(AuthenticationTestConfiguration.class)
+	static class CustomizerAndFilterChainConfig {
+
+		@Bean
+		public WebSecurityCustomizer webSecurityCustomizer() {
+			return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
+		}
+
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			return http
+					.antMatcher("/role1/**")
+					.authorizeRequests((authorize) -> authorize
+							.anyRequest().hasRole("1")
+					)
+					.build();
+			// @formatter:on
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Import(AuthenticationTestConfiguration.class)
+	static class CustomizerAndAdapterConfig {
+
+		@Bean
+		public WebSecurityCustomizer webSecurityCustomizer() {
+			return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
+		}
+
+		@Configuration
+		static class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+			@Override
+			protected void configure(HttpSecurity http) throws Exception {
+				// @formatter:off
+				http
+						.antMatcher("/role1/**")
+						.authorizeRequests((authorize) -> authorize
+								.anyRequest().hasRole("1")
+						);
+				// @formatter:on
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Import(AuthenticationTestConfiguration.class)
+	static class CustomizerAndAdapterIgnoringConfig {
+
+		@Bean
+		public WebSecurityCustomizer webSecurityCustomizer() {
+			return (web) -> web.ignoring().antMatchers("/ignore1");
+		}
+
+		@Configuration
+		static class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+			@Override
+			public void configure(WebSecurity web) throws Exception {
+				web.ignoring().antMatchers("/ignore2");
+			}
+
+		}
+
+	}
+
 }