diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java index a270f89820..b21abb5fc7 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java @@ -17,12 +17,14 @@ package org.springframework.boot.devtools.autoconfigure; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -36,25 +38,21 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration(proxyBeanMethods = false) class RemoteDevtoolsSecurityConfiguration { - @Configuration - static class SecurityConfiguration { + private final String url; - private final String url; - - SecurityConfiguration(DevToolsProperties devToolsProperties, ServerProperties serverProperties) { - ServerProperties.Servlet servlet = serverProperties.getServlet(); - String servletContextPath = (servlet.getContextPath() != null) ? servlet.getContextPath() : ""; - this.url = servletContextPath + devToolsProperties.getRemote().getContextPath() + "/restart"; - } - - @Bean - @Order(SecurityProperties.BASIC_AUTH_ORDER - 1) - SecurityFilterChain configure(HttpSecurity http) throws Exception { - http.requestMatcher(new AntPathRequestMatcher(this.url)).authorizeRequests().anyRequest().anonymous().and() - .csrf().disable(); - return http.build(); - } + RemoteDevtoolsSecurityConfiguration(DevToolsProperties devToolsProperties, ServerProperties serverProperties) { + ServerProperties.Servlet servlet = serverProperties.getServlet(); + String servletContextPath = (servlet.getContextPath() != null) ? servlet.getContextPath() : ""; + this.url = servletContextPath + devToolsProperties.getRemote().getContextPath() + "/restart"; + } + @Bean + @Order(SecurityProperties.BASIC_AUTH_ORDER - 1) + @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) + SecurityFilterChain devtoolsSecurityFilterChain(HttpSecurity http) throws Exception { + http.requestMatcher(new AntPathRequestMatcher(this.url)).authorizeRequests().anyRequest().anonymous().and() + .csrf().disable(); + return http.build(); } } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java index 551704d019..4e9dbaeabc 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java @@ -45,6 +45,8 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -157,6 +159,7 @@ class RemoteDevToolsAutoConfigurationTests { mockMvc.perform(MockMvcRequestBuilders.get(DEFAULT_CONTEXT_PATH + "/restart").header(DEFAULT_SECRET_HEADER_NAME, "supersecret")).andExpect(status().isOk()); assertRestartInvoked(true); + assertThat(this.context.containsBean("devtoolsSecurityFilterChain")).isTrue(); } @Test @@ -182,6 +185,25 @@ class RemoteDevToolsAutoConfigurationTests { mockMvc.perform(MockMvcRequestBuilders.get("/my-path")).andExpect(status().isUnauthorized()); } + @Test + void securityConfigurationWhenWebSecurityConfigurerAdapterIsFound2() throws Exception { + this.context = getContext(() -> { + AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(Config.class, PropertyPlaceholderAutoConfiguration.class, + TestWebSecurityConfigurerAdapter.class); + TestPropertyValues.of("spring.devtools.remote.secret:supersecret").applyTo(context); + context.refresh(); + return context; + }); + DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).addFilter(filter) + .build(); + mockMvc.perform(MockMvcRequestBuilders.get(DEFAULT_CONTEXT_PATH + "/restart").header(DEFAULT_SECRET_HEADER_NAME, + "supersecret")).andExpect(status().isOk()); + assertRestartInvoked(true); + } + @Test void disableRestart() throws Exception { this.context = getContext(() -> loadContext("spring.devtools.remote.secret:supersecret", @@ -250,6 +272,16 @@ class RemoteDevToolsAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class TestWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.antMatcher("/foo/**").authorizeRequests().anyRequest().authenticated().and().httpBasic(); + } + + } + /** * Mock {@link HttpRestartServer} implementation. */