Configure CORS in default security configuration for MVC
Fixes gh-11987
This commit is contained in:
		
							parent
							
								
									b6e860b2d4
								
							
						
					
					
						commit
						b26e842050
					
				|  | @ -22,6 +22,7 @@ import org.springframework.context.annotation.Configuration; | ||||||
| import org.springframework.security.config.Customizer; | import org.springframework.security.config.Customizer; | ||||||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||||||
| import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | ||||||
|  | import org.springframework.util.ClassUtils; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The default configuration for web security when the actuator dependency is on the |  * The default configuration for web security when the actuator dependency is on the | ||||||
|  | @ -44,6 +45,9 @@ class ManagementWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapte | ||||||
| 			requests.requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class)).permitAll(); | 			requests.requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class)).permitAll(); | ||||||
| 			requests.anyRequest().authenticated(); | 			requests.anyRequest().authenticated(); | ||||||
| 		}); | 		}); | ||||||
|  | 		if (ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)) { | ||||||
|  | 			http.cors(); | ||||||
|  | 		} | ||||||
| 		http.formLogin(Customizer.withDefaults()); | 		http.formLogin(Customizer.withDefaults()); | ||||||
| 		http.httpBasic(Customizer.withDefaults()); | 		http.httpBasic(Customizer.withDefaults()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -16,12 +16,17 @@ | ||||||
| 
 | 
 | ||||||
| package org.springframework.boot.actuate.autoconfigure.integrationtest; | package org.springframework.boot.actuate.autoconfigure.integrationtest; | ||||||
| 
 | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
| import org.glassfish.jersey.server.ResourceConfig; | import org.glassfish.jersey.server.ResourceConfig; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| 
 | 
 | ||||||
| import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; | import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; | ||||||
| import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; | import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; | ||||||
| import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; | import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; | ||||||
|  | import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration; | ||||||
| import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; | import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; | ||||||
| import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; | import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; | ||||||
| import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; | import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; | ||||||
|  | @ -55,18 +60,23 @@ class JerseyEndpointIntegrationTests { | ||||||
| 		testJerseyEndpoints(new Class[] { EndpointsConfiguration.class }); | 		testJerseyEndpoints(new Class[] { EndpointsConfiguration.class }); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void actuatorEndpointsWhenSecurityAvailable() { | ||||||
|  | 		WebApplicationContextRunner contextRunner = getContextRunner( | ||||||
|  | 				new Class[] { EndpointsConfiguration.class, ResourceConfigConfiguration.class }, | ||||||
|  | 				getAutoconfigurations(ManagementWebSecurityAutoConfiguration.class)); | ||||||
|  | 		contextRunner.run((context) -> { | ||||||
|  | 			int port = context.getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class) | ||||||
|  | 					.getWebServer().getPort(); | ||||||
|  | 			WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); | ||||||
|  | 			client.get().uri("/actuator").exchange().expectStatus().isUnauthorized(); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	protected void testJerseyEndpoints(Class<?>[] userConfigurations) { | 	protected void testJerseyEndpoints(Class<?>[] userConfigurations) { | ||||||
| 		FilteredClassLoader classLoader = new FilteredClassLoader(DispatcherServlet.class); | 		getContextRunner(userConfigurations, getAutoconfigurations()).run((context) -> { | ||||||
| 		new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) | 			int port = context.getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class) | ||||||
| 				.withClassLoader(classLoader) |  | ||||||
| 				.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, JerseyAutoConfiguration.class, |  | ||||||
| 						EndpointAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class, |  | ||||||
| 						WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, |  | ||||||
| 						BeansEndpointAutoConfiguration.class)) |  | ||||||
| 				.withUserConfiguration(userConfigurations) |  | ||||||
| 				.withPropertyValues("management.endpoints.web.exposure.include:*", "server.port:0").run((context) -> { |  | ||||||
| 					int port = context |  | ||||||
| 							.getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class) |  | ||||||
| 					.getWebServer().getPort(); | 					.getWebServer().getPort(); | ||||||
| 			WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); | 			WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); | ||||||
| 			client.get().uri("/actuator").exchange().expectStatus().isOk().expectBody().jsonPath("_links.beans") | 			client.get().uri("/actuator").exchange().expectStatus().isOk().expectBody().jsonPath("_links.beans") | ||||||
|  | @ -75,6 +85,23 @@ class JerseyEndpointIntegrationTests { | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	private WebApplicationContextRunner getContextRunner(Class<?>[] userConfigurations, Class<?>[] autoConfigurations) { | ||||||
|  | 		FilteredClassLoader classLoader = new FilteredClassLoader(DispatcherServlet.class); | ||||||
|  | 		return new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) | ||||||
|  | 				.withClassLoader(classLoader).withConfiguration(AutoConfigurations.of(autoConfigurations)) | ||||||
|  | 				.withUserConfiguration(userConfigurations) | ||||||
|  | 				.withPropertyValues("management.endpoints.web.exposure.include:*", "server.port:0"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private Class<?>[] getAutoconfigurations(Class<?>... additional) { | ||||||
|  | 		List<Class<?>> autoconfigurations = new ArrayList<>(Arrays.asList(JacksonAutoConfiguration.class, | ||||||
|  | 				JerseyAutoConfiguration.class, EndpointAutoConfiguration.class, | ||||||
|  | 				ServletWebServerFactoryAutoConfiguration.class, WebEndpointAutoConfiguration.class, | ||||||
|  | 				ManagementContextAutoConfiguration.class, BeansEndpointAutoConfiguration.class)); | ||||||
|  | 		autoconfigurations.addAll(Arrays.asList(additional)); | ||||||
|  | 		return autoconfigurations.toArray(new Class<?>[0]); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@ControllerEndpoint(id = "controller") | 	@ControllerEndpoint(id = "controller") | ||||||
| 	static class TestControllerEndpoint { | 	static class TestControllerEndpoint { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; | ||||||
| import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; | import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; | ||||||
| import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration; | import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration; | ||||||
| import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; | ||||||
|  | import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; | ||||||
| import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; | import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; | ||||||
| import org.springframework.boot.test.context.runner.WebApplicationContextRunner; | import org.springframework.boot.test.context.runner.WebApplicationContextRunner; | ||||||
| import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||||
|  | @ -56,7 +57,7 @@ class ManagementWebSecurityAutoConfigurationTests { | ||||||
| 	private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration( | 	private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration( | ||||||
| 			AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, | 			AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, | ||||||
| 					InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, | 					InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, | ||||||
| 					EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, | 					EndpointAutoConfiguration.class, WebMvcAutoConfiguration.class, WebEndpointAutoConfiguration.class, | ||||||
| 					SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class)); | 					SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class)); | ||||||
| 
 | 
 | ||||||
| 	@Test | 	@Test | ||||||
|  |  | ||||||
|  | @ -0,0 +1,83 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2012-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 smoketest.actuator; | ||||||
|  | 
 | ||||||
|  | import java.net.URI; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.BeforeEach; | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.boot.test.context.SpringBootTest; | ||||||
|  | import org.springframework.boot.test.web.client.LocalHostUriTemplateHandler; | ||||||
|  | import org.springframework.boot.test.web.client.TestRestTemplate; | ||||||
|  | import org.springframework.boot.web.client.RestTemplateBuilder; | ||||||
|  | import org.springframework.context.ApplicationContext; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.RequestEntity; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
|  | import org.springframework.test.context.ActiveProfiles; | ||||||
|  | 
 | ||||||
|  | import static org.assertj.core.api.Assertions.assertThat; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Integration test for cors preflight requests to management endpoints. | ||||||
|  |  * | ||||||
|  |  * @author Madhura Bhave | ||||||
|  |  */ | ||||||
|  | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | ||||||
|  | @ActiveProfiles("cors") | ||||||
|  | class CorsSampleActuatorApplicationTests { | ||||||
|  | 
 | ||||||
|  | 	private TestRestTemplate testRestTemplate; | ||||||
|  | 
 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ApplicationContext applicationContext; | ||||||
|  | 
 | ||||||
|  | 	@BeforeEach | ||||||
|  | 	void setUp() { | ||||||
|  | 		RestTemplateBuilder builder = new RestTemplateBuilder(); | ||||||
|  | 		LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(this.applicationContext.getEnvironment(), | ||||||
|  | 				"http"); | ||||||
|  | 		builder = builder.uriTemplateHandler(handler); | ||||||
|  | 		this.testRestTemplate = new TestRestTemplate(builder); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void endpointShouldReturnUnauthorized() { | ||||||
|  | 		ResponseEntity<?> entity = this.testRestTemplate.getForEntity("/actuator/env", Map.class); | ||||||
|  | 		assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void preflightRequestToEndpointShouldReturnOk() throws Exception { | ||||||
|  | 		RequestEntity<?> healthRequest = RequestEntity.options(new URI("/actuator/env")) | ||||||
|  | 				.header("Origin", "http://localhost:8080").header("Access-Control-Request-Method", "GET").build(); | ||||||
|  | 		ResponseEntity<?> exchange = this.testRestTemplate.exchange(healthRequest, Map.class); | ||||||
|  | 		assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void preflightRequestWhenCorsConfigInvalidShouldReturnForbidden() throws Exception { | ||||||
|  | 		RequestEntity<?> entity = RequestEntity.options(new URI("/actuator/env")) | ||||||
|  | 				.header("Origin", "http://localhost:9095").header("Access-Control-Request-Method", "GET").build(); | ||||||
|  | 		ResponseEntity<byte[]> exchange = this.testRestTemplate.exchange(entity, byte[].class); | ||||||
|  | 		assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,2 @@ | ||||||
|  | management.endpoints.web.cors.allowed-origins=http://localhost:8080 | ||||||
|  | management.endpoints.web.cors.allowed-methods=GET | ||||||
		Loading…
	
		Reference in New Issue