diff --git a/config/src/main/java/org/springframework/security/config/BeanIds.java b/config/src/main/java/org/springframework/security/config/BeanIds.java
index d2b54dfd56..738b6fe6c5 100644
--- a/config/src/main/java/org/springframework/security/config/BeanIds.java
+++ b/config/src/main/java/org/springframework/security/config/BeanIds.java
@@ -6,6 +6,7 @@ package org.springframework.security.config;
* These are intended for internal use.
*
* @author Ben Alex
+ * @author Luke Taylor
*/
public abstract class BeanIds {
private static final String PREFIX = "org.springframework.security.";
@@ -28,4 +29,6 @@ public abstract class BeanIds {
public static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX + "methodSecurityMetadataSourceAdvisor";
public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer";
public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource";
+
+ public static final String DEBUG_FILTER = PREFIX + "debugFilter";
}
diff --git a/config/src/main/java/org/springframework/security/config/DebugBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/DebugBeanDefinitionParser.java
new file mode 100644
index 0000000000..8173770281
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/DebugBeanDefinitionParser.java
@@ -0,0 +1,20 @@
+package org.springframework.security.config;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.security.config.debug.SecurityDebugBeanFactoryPostProcessor;
+import org.w3c.dom.Element;
+
+/**
+ * @author Luke Taylor
+ */
+public class DebugBeanDefinitionParser implements BeanDefinitionParser {
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ RootBeanDefinition debugPP = new RootBeanDefinition(SecurityDebugBeanFactoryPostProcessor.class);
+ parserContext.getReaderContext().registerWithGeneratedName(debugPP);
+
+ return null;
+ }
+}
diff --git a/config/src/main/java/org/springframework/security/config/Elements.java b/config/src/main/java/org/springframework/security/config/Elements.java
index 04b359f6a9..132ea5808e 100644
--- a/config/src/main/java/org/springframework/security/config/Elements.java
+++ b/config/src/main/java/org/springframework/security/config/Elements.java
@@ -52,4 +52,5 @@ public abstract class Elements {
@Deprecated
public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filter-invocation-definition-source";
public static final String LDAP_PASSWORD_COMPARE = "password-compare";
+ public static final String DEBUG = "debug";
}
diff --git a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java
index c86638a6d6..1633816d70 100644
--- a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java
+++ b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java
@@ -116,6 +116,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE, new MethodSecurityMetadataSourceBeanDefinitionParser());
+ parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
// Only load the web-namespace parsers if the web classes are available
if (ClassUtils.isPresent("org.springframework.security.web.FilterChainProxy", getClass().getClassLoader())) {
diff --git a/config/src/main/java/org/springframework/security/config/debug/DebugFilter.java b/config/src/main/java/org/springframework/security/config/debug/DebugFilter.java
new file mode 100644
index 0000000000..d2722264ee
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/debug/DebugFilter.java
@@ -0,0 +1,106 @@
+package org.springframework.security.config.debug;
+
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.util.RequestMatcher;
+import org.springframework.security.web.util.UrlUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Spring Security debugging filter.
+ *
+ * Logs information (such as session creation) to help the user understand how requests are being handled
+ * by Spring Security and provide them with other relevant information (such as when sessions are being created).
+ *
+ *
+ * @author Luke Taylor
+ * @since 3.1
+ */
+class DebugFilter extends OncePerRequestFilter {
+ private final FilterChainProxy fcp;
+ private final Map> filterChainMap;
+
+ public DebugFilter(FilterChainProxy fcp) {
+ this.fcp = fcp;
+ this.filterChainMap = fcp.getFilterChainMap();
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ List filters = getFilters(request);
+ Logger.log("Request received for '" + UrlUtils.buildRequestUrl(request) + "':\n\n" +
+ request + "\n\n" +
+ formatFilters(filters));
+
+ fcp.doFilter(new DebugRequestWrapper(request), response, filterChain);
+ }
+
+ String formatFilters(List filters) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Security filter chain: ");
+ if (filters == null) {
+ sb.append("no match");
+ } else if (filters.isEmpty()) {
+ sb.append("[] empty (bypassed by security='none') ");
+ } else {
+ sb.append("[\n");
+ for (Filter f : filters) {
+ sb.append(" ").append(f.getClass().getSimpleName()).append("\n");
+ }
+ sb.append("]");
+ }
+
+ return sb.toString();
+ }
+
+ private List getFilters(HttpServletRequest request) {
+ for (Map.Entry> entry : filterChainMap.entrySet()) {
+ RequestMatcher matcher = entry.getKey();
+
+ if (matcher.matches(request)) {
+ return entry.getValue();
+ }
+ }
+
+ return null;
+ }
+}
+
+class DebugRequestWrapper extends HttpServletRequestWrapper {
+
+ public DebugRequestWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ @Override
+ public HttpSession getSession() {
+ boolean sessionExists = super.getSession(false) != null;
+ HttpSession session = super.getSession();
+
+ if (!sessionExists) {
+ Logger.log("New HTTP session created: " + session.getId(), true);
+ }
+
+ return session;
+ }
+
+ @Override
+ public HttpSession getSession(boolean create) {
+ if (!create) {
+ return super.getSession(create);
+ }
+ return getSession();
+ }
+}
+
+
diff --git a/config/src/main/java/org/springframework/security/config/debug/Logger.java b/config/src/main/java/org/springframework/security/config/debug/Logger.java
new file mode 100644
index 0000000000..24b0858ddb
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/debug/Logger.java
@@ -0,0 +1,42 @@
+package org.springframework.security.config.debug;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Controls output for the Spring Security debug feature.
+ *
+ * @author Luke Taylor
+ * @since 3.1
+ */
+class Logger {
+ final static Log logger = LogFactory.getLog("Spring Security Debugger");
+
+ public static void log(String message) {
+ log(message, false);
+ }
+
+ public static void log(String message, boolean dumpStack) {
+ StringBuilder output = new StringBuilder(256);
+ output.append("\n\n************************************************************\n\n");
+ output.append(message).append("\n");
+
+ if (dumpStack) {
+ StringWriter os = new StringWriter();
+ new Exception().printStackTrace(new PrintWriter(os));
+ StringBuffer buffer = os.getBuffer();
+ // Remove the exception in case it scares people.
+ int start = buffer.indexOf("java.lang.Exception");
+ buffer.replace(start, start + 19, "");
+ output.append("\nCall stack: \n").append(os.toString());
+ }
+
+ output.append("\n\n************************************************************\n\n");
+
+ logger.info(output.toString());
+ }
+}
diff --git a/config/src/main/java/org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessor.java b/config/src/main/java/org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessor.java
new file mode 100644
index 0000000000..2dbff5d623
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessor.java
@@ -0,0 +1,28 @@
+package org.springframework.security.config.debug;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.security.config.BeanIds;
+import org.springframework.security.web.FilterChainProxy;
+
+/**
+ * @author Luke Taylor
+ */
+public class SecurityDebugBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ Logger.logger.warn("\n\n" +
+ "********************************************************************\n" +
+ "********** Security debugging is enabled. *************\n" +
+ "********** This may include sensitive information. *************\n" +
+ "********** Do not use in a production system! *************\n" +
+ "********************************************************************\n\n");
+ if (beanFactory.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN) != null) {
+ FilterChainProxy fcp = beanFactory.getBean(BeanIds.FILTER_CHAIN_PROXY, FilterChainProxy.class);
+ beanFactory.registerSingleton(BeanIds.DEBUG_FILTER, new DebugFilter(fcp));
+ // Overwrite the filter chain alias
+ beanFactory.registerAlias(BeanIds.DEBUG_FILTER, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
+ }
+ }
+}
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
index 2ac422207e..1ee992a4f2 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
@@ -41,6 +41,12 @@ data-source-ref =
## A reference to a DataSource bean
attribute data-source-ref {xsd:token}
+
+
+debug =
+ ## Enables Spring Security debugging infrastructure. This will provide human-readable (multi-line) debugging information to monitor requests coming into the security filters. This may include sensitive information, such as request parameters or headers, and should only be used in a development environment.
+ element debug {empty}
+
password-encoder =
## element which defines a password encoding strategy. Used by an authentication provider to convert submitted passwords to hashed versions, for example.
element password-encoder {password-encoder.attlist, salt-source?}
@@ -673,5 +679,4 @@ position =
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
attribute position {named-security-filter}
-
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "CONCURRENT_SESSION_FILTER" | "SECURITY_CONTEXT_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" |"BASIC_AUTH_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "SESSION_MANAGEMENT_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
index 1286a74689..a5754eba1a 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
@@ -104,6 +104,9 @@
+
+ Enables Spring Security debugging infrastructure. This will provide human-readable (multi-line) debugging information to monitor requests coming into the security filters. This may include sensitive information, such as request parameters or headers, and should only be used in a development environment.
+