From 52b5423b75842f9e54ad2fab7a64518c9ac4a6db Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 14 Feb 2018 17:15:01 -0500 Subject: [PATCH] WebSecurityConfigurerAdapterTests groovy->java Issue: gh-4939 --- .../WebSecurityConfigurerAdapterTests.groovy | 298 --------------- ...SecurityConfigurerAdapterTestsConfigs.java | 78 ---- .../WebSecurityConfigurerAdapterTests.java | 356 ++++++++++++++++++ 3 files changed, 356 insertions(+), 376 deletions(-) delete mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy delete mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTestsConfigs.java create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.java diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy deleted file mode 100644 index d216242e64..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright 2002-2013 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 org.springframework.security.core.userdetails.PasswordEncodedUser; - -import static org.junit.Assert.* -import static org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapterTestsConfigs.* - -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.context.ApplicationContext -import org.springframework.context.ApplicationListener -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.core.Ordered -import org.springframework.core.annotation.AnnotationAwareOrderComparator -import org.springframework.core.annotation.Order -import org.springframework.security.authentication.AuthenticationManager -import org.springframework.security.authentication.AuthenticationProvider -import org.springframework.security.authentication.AuthenticationTrustResolver -import org.springframework.security.authentication.DefaultAuthenticationEventPublisher -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.authentication.event.AuthenticationSuccessEvent -import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder -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.core.Authentication -import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter -import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter -import org.springframework.web.accept.ContentNegotiationStrategy -import org.springframework.web.accept.HeaderContentNegotiationStrategy -import org.springframework.web.filter.OncePerRequestFilter - -/** - * @author Rob Winch - * - */ -class WebSecurityConfigurerAdapterTests extends BaseSpringSpec { - - def "MessageSources populated on AuthenticationProviders"() { - when: - loadConfig(MessageSourcesPopulatedConfig) - List providers = authenticationProviders() - then: - providers*.messages*.messageSource == [context,context,context,context] - } - - def "messages set when using WebSecurityConfigurerAdapter"() { - when: - loadConfig(InMemoryAuthWithWebSecurityConfigurerAdapter) - then: - authenticationManager.messages.messageSource instanceof ApplicationContext - } - - def "headers are populated by default"() { - setup: "load config that overrides http and accepts defaults" - loadConfig(HeadersArePopulatedByDefaultConfig) - request.secure = true - when: "invoke the springSecurityFilterChain" - springSecurityFilterChain.doFilter(request, response, chain) - then: "the default headers are added" - responseHeaders == ['X-Content-Type-Options':'nosniff', - 'X-Frame-Options':'DENY', - 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', - 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - 'Pragma':'no-cache', - 'Expires' : '0', - 'X-XSS-Protection' : '1; mode=block'] - } - - @EnableWebSecurity - static class HeadersArePopulatedByDefaultConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()) - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - - } - } - - def "webasync populated by default"() { - when: "load config that overrides http and accepts defaults" - loadConfig(WebAsyncPopulatedByDefaultConfig) - then: "WebAsyncManagerIntegrationFilter is populated" - findFilter(WebAsyncManagerIntegrationFilter) - } - - @EnableWebSecurity - static class WebAsyncPopulatedByDefaultConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()) - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - - } - } - - def "AuthenticationEventPublisher is registered for Web configure(AuthenticationManagerBuilder auth)"() { - when: - loadConfig(InMemoryAuthWithWebSecurityConfigurerAdapter) - then: - authenticationManager.parent.eventPublisher instanceof DefaultAuthenticationEventPublisher - when: - Authentication token = new UsernamePasswordAuthenticationToken("user","password") - authenticationManager.authenticate(token) - then: "We only receive the AuthenticationSuccessEvent once" - InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS.size() == 1 - InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS[0].authentication.name == token.principal - } - - @EnableWebSecurity - static class InMemoryAuthWithWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements ApplicationListener { - static List EVENTS = [] - @Bean - @Override - public AuthenticationManager authenticationManagerBean() - throws Exception { - return super.authenticationManagerBean(); - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("{noop}password").roles("USER") - } - - @Override - public void onApplicationEvent(AuthenticationSuccessEvent e) { - EVENTS.add(e) - } - } - - def "Override ContentNegotiationStrategy with @Bean"() { - setup: - OverrideContentNegotiationStrategySharedObjectConfig.CNS = Mock(ContentNegotiationStrategy) - when: - loadConfig(OverrideContentNegotiationStrategySharedObjectConfig) - then: - context.getBean(OverrideContentNegotiationStrategySharedObjectConfig).http.getSharedObject(ContentNegotiationStrategy) == OverrideContentNegotiationStrategySharedObjectConfig.CNS - } - - @EnableWebSecurity - static class OverrideContentNegotiationStrategySharedObjectConfig extends WebSecurityConfigurerAdapter { - static ContentNegotiationStrategy CNS - - @Bean - public static ContentNegotiationStrategy cns() { - return CNS - } - } - - def "ContentNegotiationStrategy shareObject defaults to Header with no @Bean"() { - when: - loadConfig(ContentNegotiationStrategyDefaultSharedObjectConfig) - then: - context.getBean(ContentNegotiationStrategyDefaultSharedObjectConfig).http.getSharedObject(ContentNegotiationStrategy).class == HeaderContentNegotiationStrategy - } - - @EnableWebSecurity - static class ContentNegotiationStrategyDefaultSharedObjectConfig extends WebSecurityConfigurerAdapter {} - - def "UserDetailsService lazy"() { - setup: - allowCircularReferences = true - loadConfig(RequiresUserDetailsServiceConfig,UserDetailsServiceConfig) - when: - findFilter(MyFilter).userDetailsService.loadUserByUsername("user") - then: - noExceptionThrown() - when: - findFilter(MyFilter).userDetailsService.loadUserByUsername("admin") - then: - thrown(UsernameNotFoundException) - } - - @Configuration - static class RequiresUserDetailsServiceConfig { - @Bean - public MyFilter myFilter(UserDetailsService uds) { - return new MyFilter(uds) - } - } - - @EnableWebSecurity - static class UserDetailsServiceConfig extends WebSecurityConfigurerAdapter { - @Autowired - private MyFilter myFilter; - - @Bean - @Override - public UserDetailsService userDetailsServiceBean() { - return super.userDetailsServiceBean() - } - - @Override - public void configure(HttpSecurity http) { - http - .addFilterBefore(myFilter,UsernamePasswordAuthenticationFilter) - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()) - } - } - - def "SEC-2274: WebSecurityConfigurer adds ApplicationContext as a shared object"() { - when: - loadConfig(ApplicationContextSharedObjectConfig) - then: - context.getBean(ApplicationContextSharedObjectConfig).http.getSharedObject(ApplicationContext) == context - } - - @EnableWebSecurity - static class ApplicationContextSharedObjectConfig extends WebSecurityConfigurerAdapter { - - } - - static class MyFilter extends OncePerRequestFilter { - private UserDetailsService userDetailsService - public MyFilter(UserDetailsService uds) { - assert uds != null - this.userDetailsService = uds - } - public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { - chain.doFilter(request,response) - } - } - - def "AuthenticationTrustResolver populated as defaultObject"() { - setup: - CustomTrustResolverConfig.TR = Mock(AuthenticationTrustResolver) - when: - loadConfig(CustomTrustResolverConfig) - then: - context.getBean(CustomTrustResolverConfig).http.getSharedObject(AuthenticationTrustResolver) == CustomTrustResolverConfig.TR - } - - @EnableWebSecurity - static class CustomTrustResolverConfig extends WebSecurityConfigurerAdapter { - static AuthenticationTrustResolver TR - - @Bean - public static AuthenticationTrustResolver tr() { - return TR - } - } - - def "WebSecurityConfigurerAdapter has Ordered between 0 and lowest priority"() { - when: - def lowestConfig = new LowestPriorityWebSecurityConfig() - def defaultConfig = new DefaultOrderWebSecurityConfig() - def compare = new AnnotationAwareOrderComparator() - then: "the default ordering is between 0 and lowest priority (Boot adapters)" - compare.compare(lowestConfig, defaultConfig) > 0 - } - - class DefaultOrderWebSecurityConfig extends WebSecurityConfigurerAdapter {} - - @Order(Ordered.LOWEST_PRECEDENCE) - class LowestPriorityWebSecurityConfig extends WebSecurityConfigurerAdapter {} -} diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTestsConfigs.java b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTestsConfigs.java deleted file mode 100644 index 9d9b82513b..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTestsConfigs.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-2013 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 javax.sql.DataSource; - -import org.springframework.context.annotation.Bean; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.ldap.core.support.BaseLdapPathContextSource; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -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.ldap.DefaultSpringSecurityContextSource; - -/** - * @author Rob Winch - * - */ -public class WebSecurityConfigurerAdapterTestsConfigs { - - // necessary because groovy resolves incorrect method when using generics - @EnableWebSecurity - static class MessageSourcesPopulatedConfig extends WebSecurityConfigurerAdapter { - // @formatter:off - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .antMatcher("/role1/**") - .authorizeRequests() - .anyRequest().hasRole("1"); - } - // @formatter:on - - @Bean - public BaseLdapPathContextSource contextSource() throws Exception { - DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource( - "ldap://127.0.0.1:33389/dc=springframework,dc=org"); - contextSource.setUserDn("uid=admin,ou=system"); - contextSource.setPassword("secret"); - return contextSource; - } - - @Bean - public DataSource dataSource() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); - return builder.setType(EmbeddedDatabaseType.HSQL).build(); - } - - // @formatter:off - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication().and() - .jdbcAuthentication() - .dataSource(dataSource()) - .and() - .ldapAuthentication() - .userDnPatterns("uid={0},ou=people") - .contextSource(contextSource()); - } - // @formatter:on - } -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.java new file mode 100644 index 0000000000..f28576642b --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.java @@ -0,0 +1,356 @@ +/* + * Copyright 2002-2018 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 org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.test.SpringTestRule; +import org.springframework.security.core.userdetails.PasswordEncodedUser; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.ThrowableAssert.catchThrowable; +import static org.mockito.Mockito.mock; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link WebSecurityConfigurerAdapter}. + * + * @author Rob Winch + * @author Joe Grandja + */ +public class WebSecurityConfigurerAdapterTests { + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + private FilterChainProxy springSecurityFilterChain; + + @Autowired + private MockMvc mockMvc; + + @Test + public void loadConfigWhenRequestSecureThenDefaultSecurityHeadersReturned() throws Exception { + this.spring.register(HeadersArePopulatedByDefaultConfig.class).autowire(); + + this.mockMvc.perform(get("/").secure(true)) + .andExpect(header().string("X-Content-Type-Options", "nosniff")) + .andExpect(header().string("X-Frame-Options", "DENY")) + .andExpect(header().string("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains")) + .andExpect(header().string("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate")) + .andExpect(header().string("Pragma", "no-cache")) + .andExpect(header().string("Expires", "0")) + .andExpect(header().string("X-XSS-Protection", "1; mode=block")); + } + + @EnableWebSecurity + static class HeadersArePopulatedByDefaultConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + } + } + + @Test + public void loadConfigWhenDefaultConfigThenWebAsyncManagerIntegrationFilterAdded() throws Exception { + this.spring.register(WebAsyncPopulatedByDefaultConfig.class).autowire(); + + assertThat(this.findFilter(WebAsyncManagerIntegrationFilter.class, this.springSecurityFilterChain)).isNotNull(); + } + + @EnableWebSecurity + static class WebAsyncPopulatedByDefaultConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + } + } + + @Test + public void loadConfigWhenRequestAuthenticateThenAuthenticationEventPublished() throws Exception { + this.spring.register(InMemoryAuthWithWebSecurityConfigurerAdapter.class).autowire(); + + this.mockMvc.perform(formLogin()) + .andExpect(status().is3xxRedirection()); + + assertThat(InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS).isNotEmpty(); + assertThat(InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS).hasSize(1); + } + + @EnableWebSecurity + static class InMemoryAuthWithWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter + implements ApplicationListener { + + static List EVENTS = new ArrayList<>(); + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + } + + @Override + public void onApplicationEvent(AuthenticationSuccessEvent event) { + EVENTS.add(event); + } + } + + @Test + public void loadConfigWhenCustomContentNegotiationStrategyBeanThenOverridesDefault() throws Exception { + OverrideContentNegotiationStrategySharedObjectConfig.CONTENT_NEGOTIATION_STRATEGY_BEAN = mock(ContentNegotiationStrategy.class); + this.spring.register(OverrideContentNegotiationStrategySharedObjectConfig.class).autowire(); + + OverrideContentNegotiationStrategySharedObjectConfig securityConfig = + this.spring.getContext().getBean(OverrideContentNegotiationStrategySharedObjectConfig.class); + + assertThat(securityConfig.contentNegotiationStrategySharedObject).isNotNull(); + assertThat(securityConfig.contentNegotiationStrategySharedObject) + .isSameAs(OverrideContentNegotiationStrategySharedObjectConfig.CONTENT_NEGOTIATION_STRATEGY_BEAN); + } + + @EnableWebSecurity + static class OverrideContentNegotiationStrategySharedObjectConfig extends WebSecurityConfigurerAdapter { + static ContentNegotiationStrategy CONTENT_NEGOTIATION_STRATEGY_BEAN; + private ContentNegotiationStrategy contentNegotiationStrategySharedObject; + + @Bean + public ContentNegotiationStrategy contentNegotiationStrategy() { + return CONTENT_NEGOTIATION_STRATEGY_BEAN; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + this.contentNegotiationStrategySharedObject = http.getSharedObject(ContentNegotiationStrategy.class); + super.configure(http); + } + } + + @Test + public void loadConfigWhenDefaultContentNegotiationStrategyThenHeaderContentNegotiationStrategy() throws Exception { + this.spring.register(ContentNegotiationStrategyDefaultSharedObjectConfig.class).autowire(); + + ContentNegotiationStrategyDefaultSharedObjectConfig securityConfig = + this.spring.getContext().getBean(ContentNegotiationStrategyDefaultSharedObjectConfig.class); + + assertThat(securityConfig.contentNegotiationStrategySharedObject).isNotNull(); + assertThat(securityConfig.contentNegotiationStrategySharedObject).isInstanceOf(HeaderContentNegotiationStrategy.class); + } + + @EnableWebSecurity + static class ContentNegotiationStrategyDefaultSharedObjectConfig extends WebSecurityConfigurerAdapter { + private ContentNegotiationStrategy contentNegotiationStrategySharedObject; + + @Override + protected void configure(HttpSecurity http) throws Exception { + this.contentNegotiationStrategySharedObject = http.getSharedObject(ContentNegotiationStrategy.class); + super.configure(http); + } + } + + @Test + public void loadConfigWhenUserDetailsServiceHasCircularReferenceThenStillLoads() throws Exception { + this.spring.register(RequiresUserDetailsServiceConfig.class, UserDetailsServiceConfig.class).autowire(); + + MyFilter myFilter = this.findFilter(MyFilter.class, this.springSecurityFilterChain); + + Throwable thrown = catchThrowable(() -> myFilter.userDetailsService.loadUserByUsername("user") ); + assertThat(thrown).isNull(); + + thrown = catchThrowable(() -> myFilter.userDetailsService.loadUserByUsername("admin") ); + assertThat(thrown).isInstanceOf(UsernameNotFoundException.class); + } + + @Configuration + static class RequiresUserDetailsServiceConfig { + @Bean + public MyFilter myFilter(UserDetailsService userDetailsService) { + return new MyFilter(userDetailsService); + } + } + + @EnableWebSecurity + static class UserDetailsServiceConfig extends WebSecurityConfigurerAdapter { + @Autowired + private MyFilter myFilter; + + @Bean + @Override + public UserDetailsService userDetailsServiceBean() throws Exception { + return super.userDetailsServiceBean(); + } + + @Override + public void configure(HttpSecurity http) { + http.addFilterBefore(this.myFilter, UsernamePasswordAuthenticationFilter.class); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + } + } + + static class MyFilter extends OncePerRequestFilter { + private UserDetailsService userDetailsService; + + MyFilter(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + filterChain.doFilter(request, response); + } + } + + // SEC-2274: WebSecurityConfigurer adds ApplicationContext as a shared object + @Test + public void loadConfigWhenSharedObjectsCreatedThenApplicationContextAdded() throws Exception { + this.spring.register(ApplicationContextSharedObjectConfig.class).autowire(); + + ApplicationContextSharedObjectConfig securityConfig = + this.spring.getContext().getBean(ApplicationContextSharedObjectConfig.class); + + assertThat(securityConfig.applicationContextSharedObject).isNotNull(); + assertThat(securityConfig.applicationContextSharedObject).isSameAs(this.spring.getContext()); + } + + @EnableWebSecurity + static class ApplicationContextSharedObjectConfig extends WebSecurityConfigurerAdapter { + private ApplicationContext applicationContextSharedObject; + + @Override + protected void configure(HttpSecurity http) throws Exception { + this.applicationContextSharedObject = http.getSharedObject(ApplicationContext.class); + super.configure(http); + } + } + + @Test + public void loadConfigWhenCustomAuthenticationTrustResolverBeanThenOverridesDefault() throws Exception { + CustomTrustResolverConfig.AUTHENTICATION_TRUST_RESOLVER_BEAN = mock(AuthenticationTrustResolver.class); + this.spring.register(CustomTrustResolverConfig.class).autowire(); + + CustomTrustResolverConfig securityConfig = + this.spring.getContext().getBean(CustomTrustResolverConfig.class); + + assertThat(securityConfig.authenticationTrustResolverSharedObject).isNotNull(); + assertThat(securityConfig.authenticationTrustResolverSharedObject) + .isSameAs(CustomTrustResolverConfig.AUTHENTICATION_TRUST_RESOLVER_BEAN); + } + + @EnableWebSecurity + static class CustomTrustResolverConfig extends WebSecurityConfigurerAdapter { + static AuthenticationTrustResolver AUTHENTICATION_TRUST_RESOLVER_BEAN; + private AuthenticationTrustResolver authenticationTrustResolverSharedObject; + + @Bean + public AuthenticationTrustResolver authenticationTrustResolver() { + return AUTHENTICATION_TRUST_RESOLVER_BEAN; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + this.authenticationTrustResolverSharedObject = http.getSharedObject(AuthenticationTrustResolver.class); + super.configure(http); + } + } + + @Test + public void compareOrderWebSecurityConfigurerAdapterWhenLowestOrderToDefaultOrderThenGreaterThanZero() throws Exception { + AnnotationAwareOrderComparator comparator = new AnnotationAwareOrderComparator(); + assertThat(comparator.compare( + new LowestPriorityWebSecurityConfig(), + new DefaultOrderWebSecurityConfig())).isGreaterThan(0); + } + + static class DefaultOrderWebSecurityConfig extends WebSecurityConfigurerAdapter { + } + + @Order + static class LowestPriorityWebSecurityConfig extends WebSecurityConfigurerAdapter { + } + + private T findFilter(Class filterType, FilterChainProxy filterChainProxy) { + return this.findFilter(filterType, filterChainProxy, 0); + } + + private T findFilter(Class filterType, FilterChainProxy filterChainProxy, int filterChainIndex) { + if (filterChainIndex >= filterChainProxy.getFilterChains().size()) { + return null; + } + + Filter filter = filterChainProxy.getFilterChains().get(filterChainIndex).getFilters() + .stream() + .filter(f -> f.getClass().isAssignableFrom(filterType)) + .findFirst() + .orElse(null); + + return (T) filter; + } +}