Last active
July 19, 2026 02:12
-
-
Save sandipchitale/a66531379e51e45e3a243bb21839505f to your computer and use it in GitHub Desktop.
Springboot dump filters and spring security filter chains #spring-security-filter-chain
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package ?; | |
| import jakarta.servlet.Filter; | |
| import jakarta.servlet.FilterChain; | |
| import jakarta.servlet.ServletException; | |
| import jakarta.servlet.http.Cookie; | |
| import jakarta.servlet.http.HttpServletRequest; | |
| import jakarta.servlet.http.HttpServletResponse; | |
| import org.apache.catalina.core.ApplicationFilterChain; | |
| import org.apache.catalina.core.ApplicationFilterConfig; | |
| import org.springframework.beans.BeansException; | |
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | |
| import org.springframework.context.ApplicationContext; | |
| import org.springframework.context.annotation.Bean; | |
| import org.springframework.context.annotation.Configuration; | |
| import org.springframework.core.Ordered; | |
| import org.springframework.security.web.DefaultSecurityFilterChain; | |
| import org.springframework.security.web.FilterChainProxy; | |
| import org.springframework.security.web.SecurityFilterChain; | |
| import org.springframework.security.web.debug.DebugFilter; | |
| import org.springframework.security.web.util.matcher.AndRequestMatcher; | |
| import org.springframework.security.web.util.matcher.NegatedRequestMatcher; | |
| import org.springframework.security.web.util.matcher.OrRequestMatcher; | |
| import org.springframework.security.web.util.matcher.RequestMatcher; | |
| import org.springframework.util.ReflectionUtils; | |
| import org.springframework.web.filter.OncePerRequestFilter; | |
| import java.io.IOException; | |
| import java.lang.reflect.Field; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.Comparator; | |
| import java.util.List; | |
| /** | |
| * Registers a debugging filter that dumps the servlet filter chain and every configured Spring | |
| * Security {@link SecurityFilterChain} — each chain's {@link RequestMatcher} and filters — marking | |
| * the first chain that matches the current request. | |
| * | |
| * <p>Disabled by default. Enable with {@code app.dump-filters.enabled=true}. Output goes to | |
| * {@code System.out} rather than the logger — it is meant to be watched in the console next to the | |
| * request that produced it, not collected into the log file. | |
| * | |
| * <p>Spring provides built-ins that cover most of this, and they are preferable when they suffice: | |
| * <ul> | |
| * <li>{@code logging.level.org.springframework.security.web.DefaultSecurityFilterChain=DEBUG} — | |
| * logs {@code Will secure <matcher> with filters: ...} once per chain at startup.</li> | |
| * <li>{@code logging.level.org.springframework.security.web.FilterChainProxy=TRACE} — logs which | |
| * chain a request matched and each filter as it is invoked.</li> | |
| * <li>{@code @EnableWebSecurity(debug = true)} — installs {@code DebugFilter}, which logs the | |
| * request and its matched chain at INFO under the logger named {@code Spring Security Debugger}.</li> | |
| * <li>{@code /actuator/mappings} — lists servlet filter registrations without any reflection | |
| * (though as an unordered snapshot, not the real chain order).</li> | |
| * </ul> | |
| * What none of them do is expand a composite {@link RequestMatcher}: they print | |
| * {@code matcher.toString()}, so an {@code Or}/{@code And} tree — and in particular the authorization | |
| * server's {@code endpointsMatcher}, held inside a lambda — shows up as an opaque object. Expanding | |
| * that tree is what this class adds, and it is the reason it exists. | |
| * | |
| * @author schitale | |
| */ | |
| @Configuration | |
| @ConditionalOnProperty(name = "app.dump-filters.enabled", havingValue = "true") | |
| public class DumpFiltersConfig { | |
| /** URI suffixes that would otherwise flood the log with dumps for page assets. */ | |
| private static final List<String> SKIPPED_SUFFIXES = List.of(".html", ".css", ".js", ".ico", ".png", ".svg", ".map"); | |
| public static class DumpFilters extends OncePerRequestFilter { | |
| private final ApplicationContext applicationContext; | |
| /** Resolved lazily on first use: eager injection would tie this bean to the security filter's startup. */ | |
| private volatile FilterChainProxy filterChainProxy; | |
| /** | |
| * The rendered chains. Matchers and filters are fixed once the context is up, so the | |
| * reflective matcher walk runs once rather than on every request. | |
| */ | |
| private volatile List<ChainDump> chainDumps; | |
| public DumpFilters(ApplicationContext applicationContext) { | |
| this.applicationContext = applicationContext; | |
| } | |
| /** One security chain, pre-rendered, retained so {@link SecurityFilterChain#matches} can be re-evaluated per request. */ | |
| private record ChainDump(SecurityFilterChain chain, String matcherText, String filtersText) { | |
| } | |
| @Override | |
| protected boolean shouldNotFilter(HttpServletRequest request) { | |
| String uri = request.getRequestURI(); | |
| return SKIPPED_SUFFIXES.stream().anyMatch(uri::endsWith); | |
| } | |
| @Override | |
| protected void doFilterInternal(HttpServletRequest request, | |
| HttpServletResponse response, | |
| FilterChain filterChain) throws ServletException, IOException { | |
| StringBuilder out = new StringBuilder(4096); | |
| out.append("\nBegin Filters ============================\n"); | |
| out.append("URL: ").append(request.getMethod()).append(' ').append(request.getRequestURI()).append('\n'); | |
| appendHeaders(out, request); | |
| appendCookies(out, request); | |
| for (ServletFilter servletFilter : servletFilters(filterChain)) { | |
| out.append("Filter Name: ").append(servletFilter.name()) | |
| .append(" FilterClass: ").append(servletFilter.className()).append('\n'); | |
| if ("springSecurityFilterChain".equals(servletFilter.name())) { | |
| appendSecurityFilterChains(out, request); | |
| } | |
| } | |
| out.append("End Filters ==============================\n"); | |
| // Deliberately STDOUT, not the logger: this is an interactive debugging aid meant to be | |
| // read in the console as requests happen, not captured into the application log file. | |
| System.out.print(out); | |
| filterChain.doFilter(request, response); | |
| } | |
| private void appendSecurityFilterChains(StringBuilder out, HttpServletRequest request) { | |
| List<ChainDump> dumps = securityChainDumps(out); | |
| if (dumps == null) { | |
| return; | |
| } | |
| out.append("Begin Filter Chains ============================\n"); | |
| boolean firstMatched = false; | |
| for (ChainDump dump : dumps) { | |
| out.append(dump.matcherText()); | |
| if (!firstMatched && dump.chain().matches(request)) { | |
| firstMatched = true; | |
| out.append("\t\t✅ ").append(request.getMethod()).append(' ') | |
| .append(request.getRequestURI()).append(" Matched ✅\n"); | |
| } | |
| out.append(dump.filtersText()); | |
| } | |
| out.append("End Filter Chains ==============================\n"); | |
| } | |
| /** | |
| * Renders every security chain once and caches the result. The {@code springSecurityFilterChain} | |
| * bean is either the {@link FilterChainProxy} itself or a {@link DebugFilter} wrapping one — | |
| * both reachable through public API, so no reflection is needed to get at the chains. | |
| */ | |
| private List<ChainDump> securityChainDumps(StringBuilder out) { | |
| List<ChainDump> dumps = this.chainDumps; | |
| if (dumps != null) { | |
| return dumps; | |
| } | |
| FilterChainProxy proxy = securityFilterChainProxy(out); | |
| if (proxy == null) { | |
| return null; | |
| } | |
| dumps = new ArrayList<>(); | |
| for (SecurityFilterChain chain : proxy.getFilterChains()) { | |
| StringBuilder matcherText = new StringBuilder(); | |
| if (chain instanceof DefaultSecurityFilterChain defaultChain) { | |
| appendRequestMatcher(defaultChain.getRequestMatcher(), "\t", matcherText); | |
| } else { | |
| matcherText.append('\t').append(chain).append('\n'); | |
| } | |
| StringBuilder filtersText = new StringBuilder(); | |
| for (Filter securityFilter : chain.getFilters()) { | |
| filtersText.append("\t\t").append(securityFilter).append('\n'); | |
| } | |
| dumps.add(new ChainDump(chain, matcherText.toString(), filtersText.toString())); | |
| } | |
| this.chainDumps = dumps; | |
| return dumps; | |
| } | |
| private FilterChainProxy securityFilterChainProxy(StringBuilder out) { | |
| FilterChainProxy proxy = this.filterChainProxy; | |
| if (proxy != null) { | |
| return proxy; | |
| } | |
| try { | |
| Filter securityFilter = this.applicationContext.getBean("springSecurityFilterChain", Filter.class); | |
| if (securityFilter instanceof DebugFilter debugFilter) { | |
| out.append("\torg.springframework.security.web.debug.DebugFilter\n"); | |
| proxy = debugFilter.getFilterChainProxy(); | |
| } else if (securityFilter instanceof FilterChainProxy chainProxy) { | |
| proxy = chainProxy; | |
| } | |
| } catch (BeansException ex) { | |
| out.append('\t').append(ex.getMessage()).append('\n'); | |
| } | |
| this.filterChainProxy = proxy; | |
| return proxy; | |
| } | |
| } | |
| private record ServletFilter(String name, String className) { | |
| } | |
| /** | |
| * Request headers, sorted and name-aligned, one line per value so a repeated header shows each of | |
| * its values rather than a joined blob. Values are printed whole — the {@code Authorization} | |
| * bearer JWT is usually the reason you turned this dump on, so truncating it would defeat it. | |
| */ | |
| private static void appendHeaders(StringBuilder out, HttpServletRequest request) { | |
| List<String> names = Collections.list(request.getHeaderNames()); | |
| if (names.isEmpty()) { | |
| return; | |
| } | |
| names.sort(String.CASE_INSENSITIVE_ORDER); | |
| String format = "\t%-" + width(names) + "s : %s\n"; | |
| out.append("Headers:\n"); | |
| for (String name : names) { | |
| for (String value : Collections.list(request.getHeaders(name))) { | |
| out.append(format.formatted(name, value)); | |
| } | |
| } | |
| } | |
| /** | |
| * The cookies the browser sent, broken out of the raw {@code Cookie} header above — for this | |
| * project that is mainly {@code JSESSIONID}, which tells you whether the authorization-server | |
| * session survived, and whether the stateless SPA accidentally acquired one. | |
| */ | |
| private static void appendCookies(StringBuilder out, HttpServletRequest request) { | |
| Cookie[] cookies = request.getCookies(); | |
| if (cookies == null || cookies.length == 0) { | |
| return; | |
| } | |
| List<String> names = Arrays.stream(cookies).map(Cookie::getName).sorted(String.CASE_INSENSITIVE_ORDER).toList(); | |
| String format = "\t%-" + width(names) + "s = %s\n"; | |
| out.append("Cookies:\n"); | |
| Arrays.stream(cookies) | |
| .sorted(Comparator.comparing(Cookie::getName, String.CASE_INSENSITIVE_ORDER)) | |
| .forEach(cookie -> out.append(format.formatted(cookie.getName(), cookie.getValue()))); | |
| } | |
| /** Longest name, so the {@code :}/{@code =} separators line up in a column. */ | |
| private static int width(List<String> names) { | |
| return names.stream().mapToInt(String::length).max().orElse(0); | |
| } | |
| /** | |
| * The servlet filters in real invocation order. {@code ServletContext.getFilterRegistrations()} | |
| * is the public alternative but returns an unordered map, losing the ordering that makes this | |
| * dump useful — so read Tomcat's chain directly, and return nothing on any other container | |
| * rather than failing. | |
| */ | |
| private static List<ServletFilter> servletFilters(FilterChain filterChain) { | |
| if (!(filterChain instanceof ApplicationFilterChain applicationFilterChain)) { | |
| return List.of(); | |
| } | |
| Field filtersField = ReflectionUtils.findField(ApplicationFilterChain.class, "filters"); | |
| if (filtersField == null) { | |
| return List.of(); | |
| } | |
| ReflectionUtils.makeAccessible(filtersField); | |
| Object value = ReflectionUtils.getField(filtersField, applicationFilterChain); | |
| if (!(value instanceof ApplicationFilterConfig[] filterConfigs)) { | |
| return List.of(); | |
| } | |
| List<ServletFilter> servletFilters = new ArrayList<>(filterConfigs.length); | |
| for (ApplicationFilterConfig filterConfig : filterConfigs) { | |
| if (filterConfig != null) { | |
| servletFilters.add(new ServletFilter(filterConfig.getFilterName(), filterConfig.getFilterClass())); | |
| } | |
| } | |
| return servletFilters; | |
| } | |
| /** | |
| * Recursively expands a {@link RequestMatcher}. The composite matchers keep their children in | |
| * private fields with no accessor (verified against spring-security-web 7.1), so reflection is | |
| * unavoidable here — this is the part no built-in debug facility provides. | |
| */ | |
| private static void appendRequestMatcher(RequestMatcher requestMatcher, String indent, StringBuilder out) { | |
| if (requestMatcher instanceof OrRequestMatcher) { | |
| out.append(indent).append("Or\n"); | |
| appendChildren(OrRequestMatcher.class, "requestMatchers", requestMatcher, indent, out); | |
| } else if (requestMatcher instanceof AndRequestMatcher) { | |
| out.append(indent).append("And\n"); | |
| appendChildren(AndRequestMatcher.class, "requestMatchers", requestMatcher, indent, out); | |
| } else if (requestMatcher instanceof NegatedRequestMatcher) { | |
| out.append(indent).append("Not\n"); | |
| Object child = readField(NegatedRequestMatcher.class, "requestMatcher", requestMatcher); | |
| if (child instanceof RequestMatcher negated) { | |
| appendRequestMatcher(negated, indent + "\t", out); | |
| } | |
| } else { | |
| out.append(indent).append(requestMatcher).append('\n'); | |
| appendLambdaEndpointsMatcher(requestMatcher, indent, out); | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static void appendChildren(Class<?> type, String fieldName, RequestMatcher requestMatcher, | |
| String indent, StringBuilder out) { | |
| Object children = readField(type, fieldName, requestMatcher); | |
| if (children instanceof List<?> list) { | |
| ((List<RequestMatcher>) list).forEach(child -> appendRequestMatcher(child, indent + "\t", out)); | |
| } | |
| } | |
| /** | |
| * Special case for {@code OAuth2AuthorizationServerConfigurer}: its matcher is a lambda that | |
| * captures the configurer, whose {@code endpointsMatcher} holds the actual endpoint list. | |
| * Without this the authorization server chain prints as an unreadable lambda reference. | |
| */ | |
| private static void appendLambdaEndpointsMatcher(RequestMatcher requestMatcher, String indent, StringBuilder out) { | |
| Object captured = readField(requestMatcher.getClass(), "arg$1", requestMatcher); | |
| if (captured == null) { | |
| return; | |
| } | |
| Object endpointsMatcher = readField(captured.getClass(), "endpointsMatcher", captured); | |
| if (endpointsMatcher instanceof RequestMatcher matcher) { | |
| appendRequestMatcher(matcher, indent + "\t", out); | |
| } | |
| } | |
| private static Object readField(Class<?> type, String fieldName, Object target) { | |
| Field field = ReflectionUtils.findField(type, fieldName); | |
| if (field == null) { | |
| return null; | |
| } | |
| ReflectionUtils.makeAccessible(field); | |
| return ReflectionUtils.getField(field, target); | |
| } | |
| @Bean | |
| FilterRegistrationBean<DumpFilters> dumpFiltersRegistration(ApplicationContext applicationContext) { | |
| FilterRegistrationBean<DumpFilters> registrationBean = new FilterRegistrationBean<>(); | |
| registrationBean.setFilter(new DumpFilters(applicationContext)); | |
| registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); | |
| return registrationBean; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public class DumpServlets extends OncePerRequestFilter { | |
| @Override | |
| protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | |
| FilterChain filterChain) throws ServletException, IOException { | |
| System.out.println("---Servlets---"); | |
| Map<String, ? extends ServletRegistration> servletRegistrations = request.getServletContext() | |
| .getServletRegistrations(); | |
| for (Entry<String, ? extends ServletRegistration> servletNameServletRegistrationEntry : servletRegistrations | |
| .entrySet()) { | |
| System.out.println( | |
| "Servlet Name: " + servletNameServletRegistrationEntry.getKey() | |
| + " Servlet Class: " + servletNameServletRegistrationEntry.getValue().getClassName() | |
| + " Servlet Mappings: " + servletNameServletRegistrationEntry.getValue().getMappings() | |
| ); | |
| } | |
| filterChain.doFilter(request, response); | |
| } | |
| } | |
| @Bean | |
| public FilterRegistrationBean<DumpServlets> servlets() { | |
| FilterRegistrationBean<DumpServlets> registrationBean = new FilterRegistrationBean<>(); | |
| registrationBean.setFilter(new DumpServlets()); | |
| registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); | |
| return registrationBean; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| any request | |
| GET /favicon.ico Matched | |
| org.springframework.security.web.session.DisableEncodeUrlFilter@7fad214a | |
| org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@164642a4 | |
| org.springframework.security.web.context.SecurityContextHolderFilter@51bddd98 | |
| org.springframework.security.web.header.HeaderWriterFilter@4faf104 | |
| org.springframework.security.web.csrf.CsrfFilter@671ea6ff | |
| org.springframework.security.web.authentication.logout.LogoutFilter@2e43c38d | |
| org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@104dc1a2 | |
| org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@314a31b0 | |
| org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@67d32a54 | |
| org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@235b4cb8 | |
| org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@75cf0de5 | |
| org.springframework.security.web.savedrequest.RequestCacheAwareFilter@77d4ac52 | |
| org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@252744a1 | |
| org.springframework.security.web.authentication.AnonymousAuthenticationFilter@50b0afd7 | |
| org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter@4735d6e5 | |
| org.springframework.security.web.access.ExceptionTranslationFilter@49fb0bbd | |
| org.springframework.security.web.access.intercept.AuthorizationFilter@24c8d8be |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package ?; | |
| import jakarta.servlet.Filter; | |
| import jakarta.servlet.FilterChain; | |
| import jakarta.servlet.ServletException; | |
| import jakarta.servlet.http.HttpServletRequest; | |
| import jakarta.servlet.http.HttpServletResponse; | |
| import org.apache.catalina.core.ApplicationFilterChain; | |
| import org.apache.catalina.core.ApplicationFilterConfig; | |
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | |
| import org.springframework.context.annotation.Bean; | |
| import org.springframework.context.annotation.Configuration; | |
| import org.springframework.core.Ordered; | |
| import org.springframework.security.web.DefaultSecurityFilterChain; | |
| import org.springframework.security.web.FilterChainProxy; | |
| import org.springframework.security.web.SecurityFilterChain; | |
| import org.springframework.security.web.debug.DebugFilter; | |
| import org.springframework.security.web.util.matcher.AndRequestMatcher; | |
| import org.springframework.security.web.util.matcher.NegatedRequestMatcher; | |
| import org.springframework.security.web.util.matcher.OrRequestMatcher; | |
| import org.springframework.security.web.util.matcher.RequestMatcher; | |
| import org.springframework.util.ReflectionUtils; | |
| import org.springframework.web.filter.DelegatingFilterProxy; | |
| import org.springframework.web.filter.OncePerRequestFilter; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.IOException; | |
| import java.io.PrintStream; | |
| import java.lang.reflect.Field; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.List; | |
| /** | |
| * This registers a filter to dump all the configured filter and security filter chains. | |
| * | |
| * Used for debugging security filter chains configured using HttpSecurity. | |
| * | |
| * @author schitale | |
| * @since 23.4 | |
| */ | |
| @Configuration | |
| public class DumpFiltersConfig { | |
| public static class DumpFilters extends OncePerRequestFilter { | |
| @Override | |
| protected void doFilterInternal(HttpServletRequest request, | |
| HttpServletResponse response, | |
| FilterChain filterChain) throws ServletException, IOException { | |
| if (filterChain instanceof ApplicationFilterChain) { | |
| try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); | |
| PrintStream out = new PrintStream(byteArrayOutputStream);) { | |
| out.println(); | |
| out.println("Begin Filters ============================"); | |
| out.println("URL: " + request.getMethod() + " " + request.getRequestURI()); | |
| ApplicationFilterChain applicationFilterChain = (ApplicationFilterChain) filterChain; | |
| try { | |
| Field filters = applicationFilterChain.getClass().getDeclaredField("filters"); | |
| filters.setAccessible(true); | |
| ApplicationFilterConfig[] filterConfigs = (ApplicationFilterConfig[]) filters | |
| .get(applicationFilterChain); | |
| boolean firstMatched = false; | |
| for (ApplicationFilterConfig applicationFilterConfig : filterConfigs) { | |
| if (applicationFilterConfig != null) { | |
| out.println("Filter Name: " + applicationFilterConfig.getFilterName() | |
| + " FilterClass: " + applicationFilterConfig.getFilterClass()); | |
| if (applicationFilterConfig.getFilterName().equals("springSecurityFilterChain")) { | |
| try { | |
| Method getFilter = applicationFilterConfig.getClass() | |
| .getDeclaredMethod("getFilter"); | |
| getFilter.setAccessible(true); | |
| DelegatingFilterProxy delegatingFilterProxy = (DelegatingFilterProxy) getFilter | |
| .invoke(applicationFilterConfig); | |
| Field delegateField = DelegatingFilterProxy.class.getDeclaredField("delegate"); | |
| delegateField.setAccessible(true); | |
| FilterChainProxy filterChainProxy = null; | |
| if (delegateField.get(delegatingFilterProxy) instanceof FilterChainProxy) { | |
| filterChainProxy = (FilterChainProxy) delegateField.get(delegatingFilterProxy); | |
| } | |
| if (delegateField.get(delegatingFilterProxy) instanceof DebugFilter debugFilter) { | |
| // DebugFilter debugFilter = (DebugFilter) delegateField.get(delegatingFilterProxy); | |
| out.println("\torg.springframework.security.web.debug.DebugFilter"); | |
| filterChainProxy = debugFilter.getFilterChainProxy(); | |
| } | |
| if (filterChainProxy != null) { | |
| List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains(); | |
| out.println("Begin Filter Chains ============================"); | |
| for (SecurityFilterChain securityFilterChain : filterChains) { | |
| DefaultSecurityFilterChain defaultSecurityFilterChain = (DefaultSecurityFilterChain) securityFilterChain; | |
| RequestMatcher requestMatcher = defaultSecurityFilterChain.getRequestMatcher(); | |
| printRequestMatcher(requestMatcher, "\t", out); | |
| if (!firstMatched && defaultSecurityFilterChain.getRequestMatcher().matches(request)) { | |
| firstMatched = true; | |
| out.println("\t\t✅ " + request.getMethod() + " " + request.getRequestURI() + " Matched ✅"); | |
| } | |
| List<Filter> securityFilters = securityFilterChain.getFilters(); | |
| for (Filter securityFilter : securityFilters) { | |
| out.println("\t\t" + securityFilter); | |
| } | |
| } | |
| out.println("End Filter Chains =============================="); | |
| } | |
| } catch (NoSuchMethodException | InvocationTargetException e) { | |
| out.println(e.getMessage()); | |
| } | |
| } | |
| } | |
| } | |
| } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { | |
| System.err.println(e.getMessage()); | |
| } | |
| out.println("End Filters =============================="); | |
| System.out.print(byteArrayOutputStream.toString(StandardCharsets.UTF_8)); | |
| } | |
| } | |
| filterChain.doFilter(request, response); | |
| } | |
| } | |
| // Recursive method to print RequestMatcher and its sub RequestMatchers | |
| private static void printRequestMatcher(RequestMatcher requestMatcher, String indent, PrintStream out) { | |
| if (requestMatcher instanceof OrRequestMatcher orRequestMatcher) { | |
| out.println(indent + "Or"); | |
| // OrRequestMatcher orRequestMatcher = (OrRequestMatcher) requestMatcher; | |
| Field requestMatchersField = ReflectionUtils.findField(OrRequestMatcher.class, "requestMatchers"); | |
| ReflectionUtils.makeAccessible(requestMatchersField); | |
| List<RequestMatcher> requestMatchers = | |
| (List<RequestMatcher>) ReflectionUtils.getField(requestMatchersField, requestMatcher); | |
| requestMatchers.forEach((RequestMatcher rm) -> { | |
| printRequestMatcher(rm, indent + "\t", out); | |
| }); | |
| } else if (requestMatcher instanceof AndRequestMatcher andRequestMatcher) { | |
| out.println(indent + "And"); | |
| // AndRequestMatcher andRequestMatcher = (AndRequestMatcher) requestMatcher; | |
| Field requestMatchersField = ReflectionUtils.findField(AndRequestMatcher.class, "requestMatchers"); | |
| ReflectionUtils.makeAccessible(requestMatchersField); | |
| List<RequestMatcher> requestMatchers = | |
| (List<RequestMatcher>) ReflectionUtils.getField(requestMatchersField, requestMatcher); | |
| requestMatchers.forEach((RequestMatcher rm) -> { | |
| printRequestMatcher(rm, indent + "\t", out); | |
| }); | |
| } else if (requestMatcher instanceof NegatedRequestMatcher negatedRequestMatcher) { | |
| out.println(indent + "Not"); | |
| // NegatedRequestMatcher negatedRequestMatcher = (NegatedRequestMatcher) requestMatcher; | |
| Field requestMatcherField = ReflectionUtils.findField(NegatedRequestMatcher.class, "requestMatcher"); | |
| ReflectionUtils.makeAccessible(requestMatcherField); | |
| RequestMatcher rm = (RequestMatcher) ReflectionUtils.getField(requestMatcherField, requestMatcher); | |
| printRequestMatcher(rm, indent + "\t", out); | |
| } else { | |
| out.println(indent + requestMatcher); | |
| // Check if lambda - get the arg$1 | |
| Field requestMatcherField = ReflectionUtils.findField(requestMatcher.getClass(), "arg$1"); | |
| if (requestMatcherField != null) { | |
| ReflectionUtils.makeAccessible(requestMatcherField); | |
| Object o = ReflectionUtils.getField(requestMatcherField, requestMatcher); | |
| if (o != null) { | |
| // Special case of OAuth2AuthorizationServerConfigurer.endpointsMatcher | |
| Field endpointsMatcherField = ReflectionUtils.findField(o.getClass(), "endpointsMatcher"); | |
| if (endpointsMatcherField != null) { | |
| ReflectionUtils.makeAccessible(endpointsMatcherField); | |
| RequestMatcher rm = (RequestMatcher) ReflectionUtils.getField(endpointsMatcherField, o); | |
| printRequestMatcher(rm, indent + "\t", out); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| @Bean | |
| FilterRegistrationBean<DumpFilters> filters() { | |
| FilterRegistrationBean<DumpFilters> registrationBean = new FilterRegistrationBean<>(); | |
| registrationBean.setFilter(new DumpFilters()); | |
| registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); | |
| return registrationBean; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.