[bs-111] Extract trace logging from Security config
It's not really a security feature (just logging request headers), so better to put it in the main actuator autoconfig. [Fixes #49578819] [bs-111] Unresolvable cycle when separating management.port
This commit is contained in:
parent
bff41d51ff
commit
ceab9b9b33
|
@ -40,7 +40,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
|||
*/
|
||||
@Configuration
|
||||
@Import({ ManagementConfiguration.class, MetricConfiguration.class,
|
||||
ServerConfiguration.class, SecurityConfiguration.class,
|
||||
ServerConfiguration.class, SecurityConfiguration.class, TraceConfiguration.class,
|
||||
MetricFilterConfiguration.class, AuditConfiguration.class })
|
||||
public class ActuatorAutoConfiguration extends WebMvcConfigurationSupport {
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ public class ManagementConfiguration implements ApplicationContextAware, Disposa
|
|||
@ConditionalOnExpression("${server.port:8080} == ${management.port:${server.port:8080}}")
|
||||
@Configuration
|
||||
@Import({ VarzConfiguration.class, HealthzConfiguration.class,
|
||||
ShutdownConfiguration.class, TraceConfiguration.class })
|
||||
ShutdownConfiguration.class })
|
||||
public static class ManagementEndpointsConfiguration {
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ public class ManagementConfiguration implements ApplicationContextAware, Disposa
|
|||
context.setParent(this.parent);
|
||||
context.register(ManagementServerConfiguration.class,
|
||||
VarzConfiguration.class, HealthzConfiguration.class,
|
||||
ShutdownConfiguration.class, TraceConfiguration.class);
|
||||
ShutdownConfiguration.class);
|
||||
context.refresh();
|
||||
this.context = context;
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ import javax.servlet.Servlet;
|
|||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.bootstrap.actuate.security.SecurityFilterPostProcessor;
|
||||
import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
|
||||
import org.springframework.bootstrap.actuate.trace.TraceEndpoint;
|
||||
import org.springframework.bootstrap.actuate.trace.TraceRepository;
|
||||
import org.springframework.bootstrap.actuate.trace.WebRequestLoggingFilter;
|
||||
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
|
||||
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
|
||||
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
|
||||
|
@ -38,43 +38,33 @@ import org.springframework.web.servlet.DispatcherServlet;
|
|||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||
@ConditionalOnMissingBean({ TraceEndpoint.class })
|
||||
public class TraceConfiguration {
|
||||
|
||||
@Autowired
|
||||
private TraceRepository traceRepository;
|
||||
@Autowired(required = false)
|
||||
private TraceRepository traceRepository = new InMemoryTraceRepository();
|
||||
|
||||
@Configuration
|
||||
public static class SecurityFilterPostProcessorConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private TraceRepository traceRepository = new InMemoryTraceRepository();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TraceRepository.class)
|
||||
protected TraceRepository traceRepository() {
|
||||
return this.traceRepository;
|
||||
}
|
||||
|
||||
@Value("${management.dump_requests:false}")
|
||||
private boolean dumpRequests;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.security.web.SecurityFilterChain")
|
||||
public SecurityFilterPostProcessor securityFilterPostProcessor(
|
||||
BeanFactory beanFactory) {
|
||||
SecurityFilterPostProcessor processor = new SecurityFilterPostProcessor(
|
||||
this.traceRepository);
|
||||
processor.setDumpRequests(this.dumpRequests);
|
||||
return processor;
|
||||
}
|
||||
@Value("${management.dump_requests:false}")
|
||||
private boolean dumpRequests;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TraceRepository.class)
|
||||
protected TraceRepository traceRepository() {
|
||||
return this.traceRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||
@ConditionalOnMissingBean({ TraceEndpoint.class })
|
||||
public TraceEndpoint traceEndpoint() {
|
||||
return new TraceEndpoint(this.traceRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||
public WebRequestLoggingFilter securityFilterPostProcessor(BeanFactory beanFactory) {
|
||||
WebRequestLoggingFilter filter = new WebRequestLoggingFilter(this.traceRepository);
|
||||
filter.setDumpRequests(this.dumpRequests);
|
||||
return filter;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-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.bootstrap.actuate.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
|
||||
import org.springframework.bootstrap.actuate.trace.TraceRepository;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Bean post processor that adds a filter to Spring Security. The filter (optionally) logs
|
||||
* request headers at trace level and also sends the headers to a {@link TraceRepository}
|
||||
* for later analysis.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SecurityFilterPostProcessor implements BeanPostProcessor, Ordered {
|
||||
|
||||
private final static Log logger = LogFactory
|
||||
.getLog(SecurityFilterPostProcessor.class);
|
||||
private boolean dumpRequests = false;
|
||||
private List<String> ignore = Collections.emptyList();
|
||||
|
||||
private TraceRepository traceRepository = new InMemoryTraceRepository();
|
||||
|
||||
private int order = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* @param order the order to set
|
||||
*/
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param traceRepository
|
||||
*/
|
||||
public SecurityFilterPostProcessor(TraceRepository traceRepository) {
|
||||
super();
|
||||
this.traceRepository = traceRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of filter chains which should be ignored completely.
|
||||
*/
|
||||
public void setIgnore(List<String> ignore) {
|
||||
Assert.notNull(ignore);
|
||||
this.ignore = ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugging feature. If enabled, and trace logging is enabled
|
||||
*/
|
||||
public void setDumpRequests(boolean dumpRequests) {
|
||||
this.dumpRequests = dumpRequests;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
|
||||
if (!this.ignore.contains(beanName)) {
|
||||
if (bean instanceof FilterChainProxy) {
|
||||
FilterChainProxy proxy = (FilterChainProxy) bean;
|
||||
for (SecurityFilterChain filterChain : proxy.getFilterChains()) {
|
||||
processFilterChain(filterChain, beanName);
|
||||
}
|
||||
}
|
||||
if (bean instanceof SecurityFilterChain) {
|
||||
processFilterChain((SecurityFilterChain) bean, beanName);
|
||||
}
|
||||
}
|
||||
|
||||
return bean;
|
||||
|
||||
}
|
||||
|
||||
private void processFilterChain(SecurityFilterChain filterChain, String beanName) {
|
||||
logger.info("Processing security filter chain " + beanName);
|
||||
Filter loggingFilter = new WebRequestLoggingFilter(beanName);
|
||||
filterChain.getFilters().add(0, loggingFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
class WebRequestLoggingFilter implements Filter {
|
||||
|
||||
final Log logger = LogFactory.getLog(WebRequestLoggingFilter.class);
|
||||
private final String name;
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
WebRequestLoggingFilter(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
||||
Map<String, Object> trace = getTrace(request);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> headers = (Map<String, Object>) trace.get("headers");
|
||||
SecurityFilterPostProcessor.this.traceRepository.add(trace);
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Filter chain '" + this.name + "' processing request "
|
||||
+ request.getMethod() + " " + request.getRequestURI());
|
||||
if (SecurityFilterPostProcessor.this.dumpRequests) {
|
||||
try {
|
||||
this.logger.trace("Headers: "
|
||||
+ this.objectMapper.writeValueAsString(headers));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("Cannot create JSON", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getTrace(HttpServletRequest request) {
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
List<String> values = Collections.list(request.getHeaders(name));
|
||||
Object value = values;
|
||||
if (values.size() == 1) {
|
||||
value = values.get(0);
|
||||
} else if (values.isEmpty()) {
|
||||
value = "";
|
||||
}
|
||||
map.put(name, value);
|
||||
|
||||
}
|
||||
Map<String, Object> trace = new LinkedHashMap<String, Object>();
|
||||
trace.put("chain", this.name);
|
||||
trace.put("method", request.getMethod());
|
||||
trace.put("path", request.getRequestURI());
|
||||
trace.put("headers", map);
|
||||
return trace;
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright 2012-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.bootstrap.actuate.trace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class WebRequestLoggingFilter implements Filter, Ordered {
|
||||
|
||||
final Log logger = LogFactory.getLog(WebRequestLoggingFilter.class);
|
||||
|
||||
private boolean dumpRequests = false;
|
||||
|
||||
private final TraceRepository traceRepository;
|
||||
|
||||
private int order = Integer.MAX_VALUE;
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* @param traceRepository
|
||||
*/
|
||||
public WebRequestLoggingFilter(TraceRepository traceRepository) {
|
||||
this.traceRepository = traceRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param order the order to set
|
||||
*/
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugging feature. If enabled, and trace logging is enabled then web request
|
||||
* headers will be logged.
|
||||
*/
|
||||
public void setDumpRequests(boolean dumpRequests) {
|
||||
this.dumpRequests = dumpRequests;
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
||||
Map<String, Object> trace = getTrace(request);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> headers = (Map<String, Object>) trace.get("headers");
|
||||
this.traceRepository.add(trace);
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Processing request " + request.getMethod() + " "
|
||||
+ request.getRequestURI());
|
||||
if (this.dumpRequests) {
|
||||
try {
|
||||
this.logger.trace("Headers: "
|
||||
+ this.objectMapper.writeValueAsString(headers));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("Cannot create JSON", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getTrace(HttpServletRequest request) {
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
List<String> values = Collections.list(request.getHeaders(name));
|
||||
Object value = values;
|
||||
if (values.size() == 1) {
|
||||
value = values.get(0);
|
||||
} else if (values.isEmpty()) {
|
||||
value = "";
|
||||
}
|
||||
map.put(name, value);
|
||||
|
||||
}
|
||||
Map<String, Object> trace = new LinkedHashMap<String, Object>();
|
||||
trace.put("method", request.getMethod());
|
||||
trace.put("path", request.getRequestURI());
|
||||
trace.put("headers", map);
|
||||
return trace;
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
}
|
|
@ -13,14 +13,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.bootstrap.actuate.security;
|
||||
package org.springframework.bootstrap.actuate.trace;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.bootstrap.actuate.security.SecurityFilterPostProcessor;
|
||||
import org.springframework.bootstrap.actuate.security.SecurityFilterPostProcessor.WebRequestLoggingFilter;
|
||||
import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
|
||||
import org.springframework.bootstrap.actuate.trace.WebRequestLoggingFilter;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -29,17 +28,16 @@ import static org.junit.Assert.assertEquals;
|
|||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SecurityFilterPostProcessorTests {
|
||||
public class WebRequestLoggingFilterTests {
|
||||
|
||||
private SecurityFilterPostProcessor processor = new SecurityFilterPostProcessor(
|
||||
private WebRequestLoggingFilter filter = new WebRequestLoggingFilter(
|
||||
new InMemoryTraceRepository());
|
||||
|
||||
@Test
|
||||
public void filterDumpsRequest() {
|
||||
WebRequestLoggingFilter filter = this.processor.new WebRequestLoggingFilter("foo");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.addHeader("Accept", "application/json");
|
||||
Map<String, Object> trace = filter.getTrace(request);
|
||||
Map<String, Object> trace = this.filter.getTrace(request);
|
||||
assertEquals("GET", trace.get("method"));
|
||||
assertEquals("/foo", trace.get("path"));
|
||||
assertEquals("{Accept=application/json}", trace.get("headers").toString());
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.bootstrap</groupId>
|
||||
|
@ -15,12 +16,12 @@
|
|||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-logging-juli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
|
|
|
@ -182,7 +182,8 @@ public class TomcatEmbeddedServletContainerFactory extends
|
|||
context.addServletMapping("*.jspx", "jsp");
|
||||
}
|
||||
|
||||
private void customizeConnector(Connector connector) {
|
||||
// Needs to be protected so it can be used by subclasses
|
||||
protected void customizeConnector(Connector connector) {
|
||||
connector.setPort(getPort());
|
||||
if (connector.getProtocolHandler() instanceof AbstractProtocol
|
||||
&& getAddress() != null) {
|
||||
|
|
Loading…
Reference in New Issue