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

Remove need for WebSecurityConfigurerAdapter

Closes gh-8804
Eleftheria Stein 5 лет назад
Родитель
Сommit
aeafe04260

+ 7 - 4
config/src/main/java/org/springframework/security/config/annotation/web/WebSecurityConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2013 the original author or authors.
+ * 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.
@@ -23,14 +23,17 @@ import org.springframework.security.config.annotation.SecurityConfigurer;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.SecurityFilterChain;
 
 /**
  * Allows customization to the {@link WebSecurity}. In most instances users will use
- * {@link EnableWebSecurity} and create a {@link Configuration} that extends
- * {@link WebSecurityConfigurerAdapter} which will automatically be applied to the
- * {@link WebSecurity} by the {@link EnableWebSecurity} annotation.
+ * {@link EnableWebSecurity} and either create a {@link Configuration} that extends
+ * {@link WebSecurityConfigurerAdapter} or expose a {@link SecurityFilterChain} bean.
+ * Both will automatically be applied to the {@link WebSecurity} by the
+ * {@link EnableWebSecurity} annotation.
  *
  * @see WebSecurityConfigurerAdapter
+ * @see SecurityFilterChain
  *
  * @author Rob Winch
  * @since 3.2

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

@@ -283,7 +283,8 @@ public final class WebSecurity extends
 		Assert.state(
 				!securityFilterChainBuilders.isEmpty(),
 				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
-						+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+						+ "Typically this is done by exposing a SecurityFilterChain bean "
+						+ "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
 						+ "More advanced users can invoke "
 						+ WebSecurity.class.getSimpleName()
 						+ ".addSecurityFilterChainBuilder directly");

+ 3 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * 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.
@@ -74,7 +74,8 @@ import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 @Documented
 @Import({ WebSecurityConfiguration.class,
 		SpringWebMvcImportSelector.class,
-		OAuth2ImportSelector.class })
+		OAuth2ImportSelector.class,
+		HttpSecurityConfiguration.class})
 @EnableGlobalAuthentication
 @Configuration
 public @interface EnableWebSecurity {

+ 117 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

@@ -0,0 +1,117 @@
+/*
+ * 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.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
+import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.springframework.security.config.Customizer.withDefaults;
+
+/**
+ * {@link Configuration} that exposes the {@link HttpSecurity} bean.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@Configuration(proxyBeanMethods = false)
+class HttpSecurityConfiguration {
+	private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.";
+	private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";
+
+	private ObjectPostProcessor<Object> objectPostProcessor;
+
+	private AuthenticationManager authenticationManager;
+
+	private AuthenticationConfiguration authenticationConfiguration;
+
+	private ApplicationContext context;
+
+	@Autowired
+	public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
+		this.objectPostProcessor = objectPostProcessor;
+	}
+
+	@Autowired(required = false)
+	void setAuthenticationManager(AuthenticationManager authenticationManager) {
+		this.authenticationManager = authenticationManager;
+	}
+
+	@Autowired
+	public void setAuthenticationConfiguration(
+			AuthenticationConfiguration authenticationConfiguration) {
+		this.authenticationConfiguration = authenticationConfiguration;
+	}
+
+	@Autowired
+	public void setApplicationContext(ApplicationContext context) {
+		this.context = context;
+	}
+
+	@Bean(HTTPSECURITY_BEAN_NAME)
+	@Scope("prototype")
+	public HttpSecurity httpSecurity() throws Exception {
+		WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder =
+				new WebSecurityConfigurerAdapter.LazyPasswordEncoder(this.context);
+
+		AuthenticationManagerBuilder authenticationBuilder =
+				new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(this.objectPostProcessor, passwordEncoder);
+		authenticationBuilder.parentAuthenticationManager(authenticationManager());
+
+		HttpSecurity http = new HttpSecurity(objectPostProcessor, authenticationBuilder, createSharedObjects());
+		http
+				.csrf(withDefaults())
+				.addFilter(new WebAsyncManagerIntegrationFilter())
+				.exceptionHandling(withDefaults())
+				.headers(withDefaults())
+				.sessionManagement(withDefaults())
+				.securityContext(withDefaults())
+				.requestCache(withDefaults())
+				.anonymous(withDefaults())
+				.servletApi(withDefaults())
+				.logout(withDefaults())
+				.apply(new DefaultLoginPageConfigurer<>());
+
+		return http;
+	}
+
+	private AuthenticationManager authenticationManager() throws Exception {
+		if (this.authenticationManager != null) {
+			return this.authenticationManager;
+		} else {
+			return this.authenticationConfiguration.getAuthenticationManager();
+		}
+	}
+
+	private Map<Class<?>, Object> createSharedObjects() {
+		Map<Class<?>, Object> sharedObjects = new HashMap<>();
+		sharedObjects.put(ApplicationContext.class, context);
+		return sharedObjects;
+	}
+}

+ 28 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * 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.
@@ -15,6 +15,7 @@
  */
 package org.springframework.security.config.annotation.web.configuration;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import javax.servlet.Filter;
@@ -43,7 +44,9 @@ import org.springframework.security.config.crypto.RsaKeyConversionServicePostPro
 import org.springframework.security.context.DelegatingApplicationListener;
 import org.springframework.security.web.FilterChainProxy;
 import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
 import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
 
 
@@ -70,6 +73,8 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
 
 	private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
 
+	private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
+
 	private ClassLoader beanClassLoader;
 
 	@Autowired(required = false)
@@ -95,12 +100,27 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
 	public Filter springSecurityFilterChain() throws Exception {
 		boolean hasConfigurers = webSecurityConfigurers != null
 				&& !webSecurityConfigurers.isEmpty();
-		if (!hasConfigurers) {
+		boolean hasFilterChain = !securityFilterChains.isEmpty();
+		if (hasConfigurers && hasFilterChain) {
+			throw new IllegalStateException(
+					"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain." +
+							"Please select just one.");
+		}
+		if (!hasConfigurers && !hasFilterChain) {
 			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
 					.postProcess(new WebSecurityConfigurerAdapter() {
 					});
 			webSecurity.apply(adapter);
 		}
+		for (SecurityFilterChain securityFilterChain : securityFilterChains) {
+			webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
+			for (Filter filter : securityFilterChain.getFilters()) {
+				if (filter instanceof FilterSecurityInterceptor) {
+					webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
+					break;
+				}
+			}
+		}
 		return webSecurity.build();
 	}
 
@@ -158,6 +178,12 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
 		this.webSecurityConfigurers = webSecurityConfigurers;
 	}
 
+	@Autowired(required = false)
+	void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
+		securityFilterChains.sort(AnnotationAwareOrderComparator.INSTANCE);
+		this.securityFilterChains = securityFilterChains;
+	}
+
 	@Bean
 	public static BeanFactoryPostProcessor conversionServicePostProcessor() {
 		return new RsaKeyConversionServicePostProcessor();

+ 28 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurityTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * 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.
@@ -29,6 +29,7 @@ import org.springframework.security.config.test.SpringTestRule;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.userdetails.PasswordEncodedUser;
+import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.debug.DebugFilter;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -123,6 +124,32 @@ public class EnableWebSecurityTests {
 		}
 	}
 
+	@Test
+	public void securityFilterChainWhenEnableWebMvcThenAuthenticationPrincipalResolvable() throws Exception {
+		this.spring.register(SecurityFilterChainAuthenticationPrincipalConfig.class).autowire();
+
+		this.mockMvc.perform(get("/").with(authentication(new TestingAuthenticationToken("user1", "password"))))
+			.andExpect(content().string("user1"));
+	}
+
+	@EnableWebSecurity
+	@EnableWebMvc
+	static class SecurityFilterChainAuthenticationPrincipalConfig {
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			return http.build();
+		}
+
+		@RestController
+		static class AuthController {
+
+			@GetMapping("/")
+			String principal(@AuthenticationPrincipal String principal) {
+				return principal;
+			}
+		}
+	}
+
 	@Test
 	public void enableWebSecurityWhenNoConfigurationAnnotationThenBeanProxyingEnabled() {
 		this.spring.register(BeanProxyEnabledByDefaultConfig.class).autowire();

+ 248 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java

@@ -0,0 +1,248 @@
+/*
+ * 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 com.google.common.net.HttpHeaders;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.concurrent.Callable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.security.config.Customizer.withDefaults;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests for {@link HttpSecurityConfiguration}.
+ *
+ * @author Eleftheria Stein
+ */
+public class HttpSecurityConfigurationTests {
+	@Rule
+	public final SpringTestRule spring = new SpringTestRule();
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@Test
+	public void postWhenDefaultFilterChainBeanThenRespondsWithForbidden() throws Exception {
+		this.spring.register(DefaultWithFilterChainConfig.class)
+				.autowire();
+
+		this.mockMvc.perform(post("/"))
+				.andExpect(status().isForbidden());
+	}
+
+	@Test
+	public void getWhenDefaultFilterChainBeanThenDefaultHeadersInResponse() throws Exception {
+		this.spring.register(DefaultWithFilterChainConfig.class).autowire();
+
+		MvcResult mvcResult = this.mockMvc.perform(get("/").secure(true))
+				.andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff"))
+				.andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name()))
+				.andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains"))
+				.andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"))
+				.andExpect(header().string(HttpHeaders.EXPIRES, "0"))
+				.andExpect(header().string(HttpHeaders.PRAGMA, "no-cache"))
+				.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block"))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(
+				HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY,
+				HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION);
+	}
+
+	@Test
+	public void logoutWhenDefaultFilterChainBeanThenCreatesDefaultLogoutEndpoint() throws Exception {
+		this.spring.register(DefaultWithFilterChainConfig.class).autowire();
+
+		this.mockMvc.perform(post("/logout").with(csrf()))
+				.andExpect(redirectedUrl("/login?logout"));
+	}
+
+	@Test
+	public void loadConfigWhenDefaultConfigThenWebAsyncManagerIntegrationFilterAdded() throws Exception {
+		this.spring.register(DefaultWithFilterChainConfig.class, NameController.class).autowire();
+
+		MvcResult mvcResult = this.mockMvc.perform(get("/name").with(user("Bob")))
+				.andExpect(request().asyncStarted())
+				.andReturn();
+
+		this.mockMvc.perform(asyncDispatch(mvcResult))
+				.andExpect(status().isOk())
+				.andExpect(content().string("Bob"));
+	}
+
+	@RestController
+	static class NameController {
+		@GetMapping("/name")
+		public Callable<String> name() {
+			return () -> SecurityContextHolder.getContext().getAuthentication().getName();
+		}
+	}
+
+	@EnableWebSecurity
+	static class DefaultWithFilterChainConfig {
+		@Bean
+		public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			return http.build();
+		}
+	}
+
+	@Test
+	public void getWhenDefaultFilterChainBeanThenAnonymousPermitted() throws Exception {
+		this.spring.register(AuthorizeRequestsConfig.class, UserDetailsConfig.class, BaseController.class).autowire();
+
+		this.mockMvc.perform(get("/"))
+				.andExpect(status().isOk());
+	}
+
+	@EnableWebSecurity
+	static class AuthorizeRequestsConfig {
+		@Bean
+		public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			return http
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().permitAll()
+					)
+					.build();
+		}
+	}
+
+	@Test
+	public void authenticateWhenDefaultFilterChainBeanThenSessionIdChanges() throws Exception {
+		this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class).autowire();
+
+		MockHttpSession session = new MockHttpSession();
+		String sessionId = session.getId();
+
+		MvcResult result =
+				this.mockMvc.perform(post("/login")
+						.param("username", "user")
+						.param("password", "password")
+						.session(session)
+						.with(csrf()))
+						.andReturn();
+
+		assertThat(result.getRequest().getSession(false).getId()).isNotEqualTo(sessionId);
+	}
+
+	@Test
+	public void authenticateWhenDefaultFilterChainBeanThenRedirectsToSavedRequest() throws Exception {
+		this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class).autowire();
+
+		MockHttpSession session = (MockHttpSession)
+				this.mockMvc.perform(get("/messages"))
+						.andReturn().getRequest().getSession();
+
+		this.mockMvc.perform(post("/login")
+				.param("username", "user")
+				.param("password", "password")
+				.session(session)
+				.with(csrf()))
+				.andExpect(redirectedUrl("http://localhost/messages"));
+	}
+
+	@Test
+	public void authenticateWhenDefaultFilterChainBeanThenRolePrefixIsSet() throws Exception {
+		this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class, UserController.class).autowire();
+
+		this.mockMvc.perform(get("/user")
+				.with(authentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"))))
+				.andExpect(status().isOk());
+	}
+
+	@Test
+	public void loginWhenUsingDefaultsThenDefaultLoginPageGenerated() throws Exception {
+		this.spring.register(SecurityEnabledConfig.class).autowire();
+
+		this.mockMvc.perform(get("/login"))
+				.andExpect(status().isOk());
+	}
+
+	@EnableWebSecurity
+	static class SecurityEnabledConfig {
+		@Bean
+		public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			return http
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().authenticated()
+					)
+					.formLogin(withDefaults())
+					.build();
+		}
+	}
+
+	@Configuration
+	static class UserDetailsConfig {
+		@Bean
+		public UserDetailsService userDetailsService() {
+			UserDetails user = User.withDefaultPasswordEncoder()
+					.username("user")
+					.password("password")
+					.roles("USER")
+					.build();
+			return new InMemoryUserDetailsManager(user);
+		}
+	}
+
+	@RestController
+	static class BaseController {
+		@GetMapping("/")
+		public void index() {
+		}
+	}
+
+	@RestController
+	static class UserController {
+		@GetMapping("/user")
+		public void user(HttpServletRequest request) {
+			if (!request.isUserInRole("USER")) {
+				throw new AccessDeniedException("This resource is only available to users");
+			}
+		}
+	}
+}

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

@@ -170,6 +170,76 @@ public class WebSecurityConfigurationTests {
 		}
 	}
 
+	@Test
+	public void loadConfigWhenSecurityFilterChainsHaveOrderThenFilterChainsOrdered() {
+		this.spring.register(SortedSecurityFilterChainConfig.class).autowire();
+
+		FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
+		List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
+		assertThat(filterChains).hasSize(4);
+
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+
+		request.setServletPath("/role1/**");
+		assertThat(filterChains.get(0).matches(request)).isTrue();
+
+		request.setServletPath("/role2/**");
+		assertThat(filterChains.get(1).matches(request)).isTrue();
+
+		request.setServletPath("/role3/**");
+		assertThat(filterChains.get(2).matches(request)).isTrue();
+
+		request.setServletPath("/**");
+		assertThat(filterChains.get(3).matches(request)).isTrue();
+	}
+
+	@EnableWebSecurity
+	@Import(AuthenticationTestConfiguration.class)
+	static class SortedSecurityFilterChainConfig {
+
+		@Order(1)
+		@Bean
+		SecurityFilterChain filterChain1(HttpSecurity http) throws Exception {
+			return http
+					.antMatcher("/role1/**")
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().hasRole("1")
+					)
+					.build();
+		}
+
+		@Order(2)
+		@Bean
+		SecurityFilterChain filterChain2(HttpSecurity http) throws Exception {
+			return http
+					.antMatcher("/role2/**")
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().hasRole("2")
+					)
+					.build();
+		}
+
+		@Order(3)
+		@Bean
+		SecurityFilterChain filterChain3(HttpSecurity http) throws Exception {
+			return http
+					.antMatcher("/role3/**")
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().hasRole("3")
+					)
+					.build();
+		}
+
+		@Bean
+		SecurityFilterChain filterChain4(HttpSecurity http) throws Exception {
+			return http
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().hasRole("4")
+					)
+					.build();
+		}
+	}
+
 	@Test
 	public void loadConfigWhenWebSecurityConfigurersHaveSameOrderThenThrowBeanCreationException() {
 		Throwable thrown = catchThrowable(() -> this.spring.register(DuplicateOrderConfig.class).autowire());
@@ -372,6 +442,26 @@ public class WebSecurityConfigurationTests {
 		}
 	}
 
+	@Test
+	public void loadConfigWhenSecurityFilterChainBeanThenDefaultWebInvocationPrivilegeEvaluatorIsRegistered() {
+		this.spring.register(AuthorizeRequestsFilterChainConfig.class).autowire();
+
+		assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
+			.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
+	}
+
+	@EnableWebSecurity
+	static class AuthorizeRequestsFilterChainConfig {
+		@Bean
+		public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			return http
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().authenticated()
+					)
+					.build();
+		}
+	}
+
 	// SEC-2303
 	@Test
 	public void loadConfigWhenDefaultSecurityExpressionHandlerThenBeanResolverSet() throws Exception {
@@ -499,4 +589,42 @@ public class WebSecurityConfigurationTests {
 			}
 		}
 	}
+
+	@Test
+	public void loadConfigWhenBothAdapterAndFilterChainConfiguredThenException() {
+		Throwable thrown = catchThrowable(() -> this.spring.register(AdapterAndFilterChainConfig.class).autowire());
+
+		assertThat(thrown).isInstanceOf(BeanCreationException.class)
+				.hasRootCauseExactlyInstanceOf(IllegalStateException.class)
+				.hasMessageContaining("Found WebSecurityConfigurerAdapter as well as SecurityFilterChain.");
+
+	}
+
+	@EnableWebSecurity
+	@Import(AuthenticationTestConfiguration.class)
+	static class AdapterAndFilterChainConfig {
+		@Order(1)
+		@Configuration
+		static class WebConfigurer extends WebSecurityConfigurerAdapter {
+			@Override
+			protected void configure(HttpSecurity http) throws Exception {
+				http
+						.antMatcher("/config/**")
+						.authorizeRequests(authorize -> authorize
+								.anyRequest().permitAll()
+						);
+			}
+		}
+
+		@Order(2)
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			return http
+					.antMatcher("/filter/**")
+					.authorizeRequests(authorize -> authorize
+							.anyRequest().authenticated()
+					)
+					.build();
+		}
+	}
 }