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. +