Переглянути джерело

SEC-1469: Initial support for debugging filter.

Luke Taylor 15 роки тому
батько
коміт
1f520b691f

+ 3 - 0
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.
  * These are intended for internal use.
  *
  *
  * @author Ben Alex
  * @author Ben Alex
+ * @author Luke Taylor
  */
  */
 public abstract class BeanIds {
 public abstract class BeanIds {
     private static final String PREFIX = "org.springframework.security.";
     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 METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX + "methodSecurityMetadataSourceAdvisor";
     public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer";
     public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer";
     public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource";
     public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource";
+
+    public static final String DEBUG_FILTER = PREFIX + "debugFilter";
 }
 }

+ 20 - 0
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;
+    }
+}

+ 1 - 0
config/src/main/java/org/springframework/security/config/Elements.java

@@ -52,4 +52,5 @@ public abstract class Elements {
     @Deprecated
     @Deprecated
     public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filter-invocation-definition-source";
     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 LDAP_PASSWORD_COMPARE = "password-compare";
+    public static final String DEBUG = "debug";
 }
 }

+ 1 - 0
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.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
         parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
         parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
         parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE, new MethodSecurityMetadataSourceBeanDefinitionParser());
         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
         // Only load the web-namespace parsers if the web classes are available
         if (ClassUtils.isPresent("org.springframework.security.web.FilterChainProxy", getClass().getClassLoader())) {
         if (ClassUtils.isPresent("org.springframework.security.web.FilterChainProxy", getClass().getClassLoader())) {

+ 106 - 0
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.
+ * <p>
+ * 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<RequestMatcher, List<Filter>> 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<Filter> 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<Filter> 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<Filter> getFilters(HttpServletRequest request)  {
+        for (Map.Entry<RequestMatcher, List<Filter>> 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();
+    }
+}
+
+

+ 42 - 0
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());
+    }
+}

+ 28 - 0
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);
+        }
+    }
+}

+ 6 - 1
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
     ## A reference to a DataSource bean
     attribute data-source-ref {xsd:token}
     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 =
 password-encoder =
     ## element which defines a password encoding strategy. Used by an authentication provider to convert submitted passwords to hashed versions, for example.
     ## 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?}
     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.
     ## 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}
     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"
 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"

+ 3 - 0
config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd

@@ -104,6 +104,9 @@
       </xs:annotation>
       </xs:annotation>
     </xs:attribute>
     </xs:attribute>
   </xs:attributeGroup>
   </xs:attributeGroup>
+  <xs:element name="debug"><xs:annotation>
+      <xs:documentation>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.</xs:documentation>
+    </xs:annotation><xs:complexType/></xs:element>
   
   
   <xs:attributeGroup name="password-encoder.attlist">
   <xs:attributeGroup name="password-encoder.attlist">
     <xs:attribute name="ref" type="xs:token">
     <xs:attribute name="ref" type="xs:token">