Commit 340078ef by Johannes Stelzer

Use HandlerInterceptor so CORS-Headers are only served on Endpoints

parent 5e0c6d7b
...@@ -17,10 +17,10 @@ package de.codecentric.boot.admin.config; ...@@ -17,10 +17,10 @@ package de.codecentric.boot.admin.config;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer;
import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
...@@ -37,14 +37,11 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; ...@@ -37,14 +37,11 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.ServletWrappingController; import org.springframework.web.servlet.mvc.ServletWrappingController;
import de.codecentric.boot.admin.actuate.LogfileMvcEndpoint; import de.codecentric.boot.admin.actuate.LogfileMvcEndpoint;
import de.codecentric.boot.admin.services.SpringBootAdminRegistrator; import de.codecentric.boot.admin.services.SpringBootAdminRegistrator;
import de.codecentric.boot.admin.web.BasicAuthHttpRequestInterceptor; import de.codecentric.boot.admin.web.BasicAuthHttpRequestInterceptor;
import de.codecentric.boot.admin.web.EndpointCorsFilter;
import de.codecentric.boot.admin.web.EndpointCorsInterceptor; import de.codecentric.boot.admin.web.EndpointCorsInterceptor;
/** /**
...@@ -108,14 +105,19 @@ public class SpringBootAdminClientAutoConfiguration { ...@@ -108,14 +105,19 @@ public class SpringBootAdminClientAutoConfiguration {
} }
} }
@Bean
protected EndpointCorsInterceptor endpointCorsInterceptor() {
return new EndpointCorsInterceptor();
}
/**
* HTTP filter to enable Cross-Origin Resource Sharing.
*/
@Bean @Bean
@ConditionalOnMissingBean protected EndpointHandlerMappingCustomizer endpointHandlerMappingCustomizer() {
public EndpointCorsFilter endpointCorsFilter(EndpointHandlerMapping endpointHandlerMapping) { return new EndpointHandlerMappingCustomizer() {
return new EndpointCorsFilter(endpointHandlerMapping); @Override
public void customize(EndpointHandlerMapping mapping) {
mapping.setInterceptors(new Object[] { endpointCorsInterceptor() });
}
};
} }
@Autowired @Autowired
...@@ -124,55 +126,32 @@ public class SpringBootAdminClientAutoConfiguration { ...@@ -124,55 +126,32 @@ public class SpringBootAdminClientAutoConfiguration {
@Bean @Bean
public ApplicationListener<EmbeddedServletContainerInitializedEvent> appListener() { public ApplicationListener<EmbeddedServletContainerInitializedEvent> appListener() {
/* /*
* In case a second servletContainer is fired up (because server.port !=
* managament port), there is no viable way to register the endpointCorsFilter.
*
* Instead we register an HandlerInterceptor for the Endpoint handler mapping and
* Set jolokias AgentServlet to support Options request and the Dispatcher servlet * Set jolokias AgentServlet to support Options request and the Dispatcher servlet
* to forward such. * to forward such. Done in this nasty way in case a second servlet-container is
* Also @see https://github.com/spring-projects/spring-boot/issues/1987 * spun up, when management.port != server.port Also @see
* https://github.com/spring-projects/spring-boot/issues/1987
*/ */
return new ApplicationListener<EmbeddedServletContainerInitializedEvent>() { return new ApplicationListener<EmbeddedServletContainerInitializedEvent>() {
@Override @Override
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
if ("management".equals(event.getApplicationContext().getNamespace())) { // set DispatcherServlet to forward OptionsRequest
// register HandlerIntercepor for (DispatcherServlet servlet : event.getApplicationContext()
for (EndpointHandlerMapping handlerMapping : event.getApplicationContext() .getBeansOfType(DispatcherServlet.class).values()) {
.getBeansOfType(EndpointHandlerMapping.class).values()) { servlet.setDispatchOptionsRequest(true);
try { }
Field interceptorsField = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
interceptorsField.setAccessible(true);
@SuppressWarnings("unchecked")
List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) interceptorsField
.get(handlerMapping);
EndpointCorsInterceptor interceptor = new EndpointCorsInterceptor();
event.getApplicationContext().getBeanFactory().autowireBean(interceptor);
adaptedInterceptors.add(interceptor);
}
catch (Exception ex) {
throw new RuntimeException("Couldn't add handlerInterceptor for cors", ex);
}
}
// set DispatcherServlet to forward OptionsRequest // set Jolokias ServletWrappingController to support OPTIONS
for (DispatcherServlet servlet : event.getApplicationContext() for (JolokiaMvcEndpoint jolokiaMvcEndpoint : SpringBootAdminClientAutoConfiguration.this.applicationContext
.getBeansOfType(DispatcherServlet.class).values()) { .getBeansOfType(JolokiaMvcEndpoint.class).values()) {
servlet.setDispatchOptionsRequest(true); try {
Field controllerField = JolokiaMvcEndpoint.class.getDeclaredField("controller");
ReflectionUtils.makeAccessible(controllerField);
ServletWrappingController controller = (ServletWrappingController) controllerField
.get(jolokiaMvcEndpoint);
controller.setSupportedMethods("GET", "HEAD", "POST", "OPTIONS");
} }
catch (Exception ex) {
// set Jolokias ServletWrappingController to support OPTIONS throw new RuntimeException("Couldn't reconfigure servletWrappingController for Jolokia", ex);
for (JolokiaMvcEndpoint jolokiaMvcEndpoint : SpringBootAdminClientAutoConfiguration.this.applicationContext
.getBeansOfType(JolokiaMvcEndpoint.class).values()) {
try {
Field controllerField = JolokiaMvcEndpoint.class.getDeclaredField("controller");
ReflectionUtils.makeAccessible(controllerField);
ServletWrappingController controller = (ServletWrappingController) controllerField
.get(jolokiaMvcEndpoint);
controller.setSupportedMethods("GET", "HEAD", "POST", "OPTIONS");
}
catch (Exception ex) {
throw new RuntimeException("Couldn't reconfigure servletWrappingController for Jolokia", ex);
}
} }
} }
} }
......
/*
* Copyright 2014 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 de.codecentric.boot.admin.web;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* Request filter to allow Cross-Origin Resource Sharing.
*/
public class EndpointCorsFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(EndpointCorsFilter.class);
// Configurable origin for CORS - default: * (all)
@Value("${http.filter.cors.origin:*}")
private String origin;
@Value("${http.filter.cors.headers:Origin, X-Requested-With, Content-Type, Accept}")
private String headers;
private final EndpointHandlerMapping endpointHandlerMapping;
public EndpointCorsFilter(EndpointHandlerMapping endpointHandlerMapping) {
this.endpointHandlerMapping = endpointHandlerMapping;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
if (!endpointHandlerMapping.isDisabled() && endpointHandlerMapping.getHandler(request) != null) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Headers", headers);
}
}
catch (Exception ex) {
LOGGER.warn("Error occured while adding CORS-Headers", ex);
}
filterChain.doFilter(request, response);
}
public void setOrigin(String origin) {
this.origin = origin;
}
public String getOrigin() {
return origin;
}
public String getHeaders() {
return headers;
}
public void setHeaders(String headers) {
this.headers = headers;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment