/* * Copyright 2012-2017 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.boot.autoconfigure.security; import java.util.EnumSet; import javax.servlet.DispatcherType; import org.junit.After; import org.junit.Test; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.mock.web.MockServletContext; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; 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.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; /** * Tests for {@link SecurityAutoConfiguration}. * * @author Dave Syer * @author Rob Winch * @author Andy Wilkinson */ public class SecurityAutoConfigurationTests { private AnnotationConfigWebApplicationContext context; @After public void close() { if (this.context != null) { this.context.close(); } } @Test public void testWebConfiguration() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull(); // 1 for static resources and one for the rest assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains()) .hasSize(2); } @Test public void testDefaultFilterOrderWithSecurityAdapter() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(WebSecurity.class, SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean("securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class).getOrder()).isEqualTo( FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100); } @Test public void testFilterIsNotRegisteredInNonWeb() throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); try { context.refresh(); assertThat(context.containsBean("securityFilterChainRegistration")).isFalse(); } finally { context.close(); } } @Test public void testDefaultFilterOrder() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean("securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class).getOrder()).isEqualTo( FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100); } @Test public void testCustomFilterOrder() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "security.filter-order:12345"); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean("securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class).getOrder()).isEqualTo(12345); } @Test public void testDisableIgnoredStaticApplicationPaths() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); EnvironmentTestUtils.addEnvironment(this.context, "security.ignored:none"); this.context.refresh(); // Just the application endpoints now assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains()) .hasSize(1); } @Test public void testDisableBasicAuthOnApplicationPaths() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false"); this.context.refresh(); // Ignores and the "matches-none" filter only assertThat(this.context.getBeanNamesForType(FilterChainProxy.class).length) .isEqualTo(1); } @Test public void testAuthenticationManagerCreated() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(AuthenticationManager.class)).isNotNull(); } @Test public void testEventPublisherInjected() throws Exception { testAuthenticationManagerCreated(); pingAuthenticationListener(); } private void pingAuthenticationListener() { AuthenticationListener listener = new AuthenticationListener(); this.context.addApplicationListener(listener); AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); try { manager.authenticate(new UsernamePasswordAuthenticationToken("foo", "wrong")); fail("Expected BadCredentialsException"); } catch (BadCredentialsException e) { // expected } assertThat(listener.event) .isInstanceOf(AuthenticationFailureBadCredentialsEvent.class); } @Test public void testOverrideAuthenticationManager() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(TestAuthenticationConfiguration.class, SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(AuthenticationManager.class)) .isEqualTo(this.context.getBean( TestAuthenticationConfiguration.class).authenticationManager); } @Test public void testDefaultAuthenticationManagerMakesUserDetailsAvailable() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(UserDetailsSecurityCustomizer.class, SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(UserDetailsSecurityCustomizer.class) .getUserDetails().loadUserByUsername("user")).isNotNull(); } @Test public void testOverrideAuthenticationManagerAndInjectIntoSecurityFilter() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(TestAuthenticationConfiguration.class, SecurityCustomizer.class, SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(AuthenticationManager.class)) .isEqualTo(this.context.getBean( TestAuthenticationConfiguration.class).authenticationManager); } @Test public void testOverrideAuthenticationManagerWithBuilderAndInjectIntoSecurityFilter() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(AuthenticationManagerCustomizer.class, SecurityCustomizer.class, SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken( "foo", "bar", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); assertThat(this.context.getBean(AuthenticationManager.class).authenticate(user)) .isNotNull(); pingAuthenticationListener(); } @Test public void testOverrideAuthenticationManagerWithBuilderAndInjectBuilderIntoSecurityFilter() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(AuthenticationManagerCustomizer.class, WorkaroundSecurityCustomizer.class, SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken( "foo", "bar", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); assertThat(this.context.getBean(AuthenticationManager.class).authenticate(user)) .isNotNull(); } @Test public void testJpaCoexistsHappily() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.url:jdbc:hsqldb:mem:testsecdb"); EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); this.context.register(EntityConfiguration.class, PropertyPlaceholderAutoConfiguration.class, DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, SecurityAutoConfiguration.class); // This can fail if security @Conditionals force early instantiation of the // HibernateJpaAutoConfiguration (e.g. the EntityManagerFactory is not found) this.context.refresh(); assertThat(this.context.getBean(JpaTransactionManager.class)).isNotNull(); } @Test public void testDefaultUsernamePassword() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class); this.context.refresh(); SecurityProperties security = this.context.getBean(SecurityProperties.class); AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( security.getUser().getName(), security.getUser().getPassword()); assertThat(manager.authenticate(token)).isNotNull(); } @Test public void testCustomAuthenticationDoesNotAuthenticateWithBootSecurityUser() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(AuthenticationManagerCustomizer.class, SecurityAutoConfiguration.class); this.context.refresh(); SecurityProperties security = this.context.getBean(SecurityProperties.class); AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( security.getUser().getName(), security.getUser().getPassword()); try { manager.authenticate(token); fail("Expected Exception"); } catch (AuthenticationException success) { // Expected } token = new UsernamePasswordAuthenticationToken("foo", "bar"); assertThat(manager.authenticate(token)).isNotNull(); } @Test public void testSecurityEvaluationContextExtensionSupport() { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(AuthenticationManagerCustomizer.class, SecurityAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(SecurityEvaluationContextExtension.class)) .isNotNull(); } @Test public void defaultFilterDispatcherTypes() { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); DelegatingFilterProxyRegistrationBean bean = this.context.getBean( "securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class); @SuppressWarnings("unchecked") EnumSet<DispatcherType> dispatcherTypes = (EnumSet<DispatcherType>) ReflectionTestUtils .getField(bean, "dispatcherTypes"); assertThat(dispatcherTypes).containsOnly(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST); } @Test public void customFilterDispatcherTypes() { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); EnvironmentTestUtils.addEnvironment(this.context, "security.filter-dispatcher-types:INCLUDE,ERROR"); this.context.refresh(); DelegatingFilterProxyRegistrationBean bean = this.context.getBean( "securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class); @SuppressWarnings("unchecked") EnumSet<DispatcherType> dispatcherTypes = (EnumSet<DispatcherType>) ReflectionTestUtils .getField(bean, "dispatcherTypes"); assertThat(dispatcherTypes).containsOnly(DispatcherType.INCLUDE, DispatcherType.ERROR); } private static final class AuthenticationListener implements ApplicationListener<AbstractAuthenticationEvent> { private ApplicationEvent event; @Override public void onApplicationEvent(AbstractAuthenticationEvent event) { this.event = event; } } @Configuration @TestAutoConfigurationPackage(City.class) protected static class EntityConfiguration { } @Configuration protected static class TestAuthenticationConfiguration { private AuthenticationManager authenticationManager; @Bean public AuthenticationManager myAuthenticationManager() { this.authenticationManager = new AuthenticationManager() { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return new TestingAuthenticationToken("foo", "bar"); } }; return this.authenticationManager; } } @Configuration protected static class SecurityCustomizer extends WebSecurityConfigurerAdapter { final AuthenticationManager authenticationManager; protected SecurityCustomizer(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } } @Configuration protected static class WorkaroundSecurityCustomizer extends WebSecurityConfigurerAdapter { private final AuthenticationManagerBuilder builder; @SuppressWarnings("unused") private AuthenticationManager authenticationManager; protected WorkaroundSecurityCustomizer(AuthenticationManagerBuilder builder) { this.builder = builder; } @Override protected void configure(HttpSecurity http) throws Exception { this.authenticationManager = new AuthenticationManager() { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return WorkaroundSecurityCustomizer.this.builder.getOrBuild() .authenticate(authentication); } }; } } @Configuration @Order(-1) protected static class AuthenticationManagerCustomizer extends GlobalAuthenticationConfigurerAdapter { @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("foo").password("bar").roles("USER"); } } @Configuration protected static class UserDetailsSecurityCustomizer extends WebSecurityConfigurerAdapter { private UserDetailsService userDetails; @Override protected void configure(HttpSecurity http) throws Exception { this.userDetails = http.getSharedObject(UserDetailsService.class); } public UserDetailsService getUserDetails() { return this.userDetails; } } @Configuration @EnableWebSecurity static class WebSecurity extends WebSecurityConfigurerAdapter { } }