Skip to content

Instantly share code, notes, and snippets.

@sandipchitale
Last active February 11, 2025 05:27
Show Gist options
  • Save sandipchitale/a66531379e51e45e3a243bb21839505f to your computer and use it in GitHub Desktop.
Save sandipchitale/a66531379e51e45e3a243bb21839505f to your computer and use it in GitHub Desktop.
Springboot dump filters and spring security filter chains #spring-security-filter-chain
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;
}
}
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;
}
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 is the request matcher used by Spring Security OAuth2 Athorization Server (Note: some of the paths repeat):
org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer$$Lambda$780/0x0000000800fc7c10@468e8565
Or
Or
Ant [pattern='/oauth2/token', POST]
Ant [pattern='/oauth2/introspect', POST]
Ant [pattern='/oauth2/revoke', POST]
Ant [pattern='/oauth2/device_authorization', POST]
Ant [pattern='/.well-known/oauth-authorization-server', GET]
Or
Ant [pattern='/oauth2/authorize', GET]
Ant [pattern='/oauth2/authorize', POST]
Ant [pattern='/oauth2/token', POST]
Ant [pattern='/oauth2/introspect', POST]
Ant [pattern='/oauth2/revoke', POST]
Ant [pattern='/oauth2/device_authorization', POST]
Or
Ant [pattern='/oauth2/device_verification', GET]
Ant [pattern='/oauth2/device_verification', POST]
Or
Ant [pattern='/.well-known/openid-configuration', GET]
Or
Ant [pattern='/connect/logout', GET]
Ant [pattern='/connect/logout', POST]
Or
Ant [pattern='/userinfo', GET]
Ant [pattern='/userinfo', POST]
Ant [pattern='/oauth2/jwks', GET]
And the full filter chain:
Begin Filters ============================
URL: GET /oauth2/jwks
Filter Name: filters FilterClass: com.example.authserver.DumpFiltersConfig$DumpFilters
Filter Name: characterEncodingFilter FilterClass: org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter
Filter Name: formContentFilter FilterClass: org.springframework.boot.web.servlet.filter.OrderedFormContentFilter
Filter Name: requestContextFilter FilterClass: org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter
Filter Name: springSecurityFilterChain FilterClass: org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1
Begin Filter Chains ============================
org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer$$Lambda$780/0x0000000800fc7c10@468e8565
Or
Or
Ant [pattern='/oauth2/token', POST]
Ant [pattern='/oauth2/introspect', POST]
Ant [pattern='/oauth2/revoke', POST]
Ant [pattern='/oauth2/device_authorization', POST]
Ant [pattern='/.well-known/oauth-authorization-server', GET]
Or
Ant [pattern='/oauth2/authorize', GET]
Ant [pattern='/oauth2/authorize', POST]
Ant [pattern='/oauth2/token', POST]
Ant [pattern='/oauth2/introspect', POST]
Ant [pattern='/oauth2/revoke', POST]
Ant [pattern='/oauth2/device_authorization', POST]
Or
Ant [pattern='/oauth2/device_verification', GET]
Ant [pattern='/oauth2/device_verification', POST]
Or
Ant [pattern='/.well-known/openid-configuration', GET]
Or
Ant [pattern='/connect/logout', GET]
Ant [pattern='/connect/logout', POST]
Or
Ant [pattern='/userinfo', GET]
Ant [pattern='/userinfo', POST]
Ant [pattern='/oauth2/jwks', GET]
GET /oauth2/jwks Matched
org.springframework.security.web.session.DisableEncodeUrlFilter@91da29b
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7066363
org.springframework.security.web.context.SecurityContextHolderFilter@41ccb3b9
-> org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.AuthorizationServerContextFilter@3443e476
org.springframework.security.web.header.HeaderWriterFilter@3b353651
org.springframework.security.web.csrf.CsrfFilter@63f855b
-> org.springframework.security.oauth2.server.authorization.oidc.web.OidcLogoutEndpointFilter@2091833
org.springframework.security.web.authentication.logout.LogoutFilter@626c19cf
-> org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter@9f86dc3
-> org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter@2f80cb79
-> org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter@fe09383
-> org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter@1de0641b
-> org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter@7f287b98
-> org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter@2e1b374c
org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter@67bd351e
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1e84f3c8
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5d5a51b1
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@75b38c36
org.springframework.security.web.access.ExceptionTranslationFilter@3913f206
org.springframework.security.web.access.intercept.AuthorizationFilter@7161457
-> org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter@35c4697c
-> org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter@70d77826
-> org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter@4e3e11b9
-> org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceAuthorizationEndpointFilter@49e2b3c5
-> org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter@7ba93755
any request
org.springframework.security.web.session.DisableEncodeUrlFilter@6f8e9d06
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@77d381e6
org.springframework.security.web.context.SecurityContextHolderFilter@7f7c420c
org.springframework.security.web.header.HeaderWriterFilter@66383c29
org.springframework.security.web.csrf.CsrfFilter@3f63a513
org.springframework.security.web.authentication.logout.LogoutFilter@d7bbf12
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3a36cd5
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@6435fa1c
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@2272cbb0
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5d152bcd
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@43cb5f38
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3f6f3cc
org.springframework.security.web.access.ExceptionTranslationFilter@413bef78
org.springframework.security.web.access.intercept.AuthorizationFilter@5f7eee96
End Filter Chains ==============================
@sandipchitale
Copy link
Author

sandipchitale commented Oct 17, 2020

  • Sample output:
Filter Name: dumpFilters FilterClass: com.example.oauth2arc.Oauth2arcApplication$DumpFilters
Filter Name: characterEncodingFilter FilterClass: org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter
Filter Name: webMvcMetricsFilter FilterClass: org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter
Filter Name: sessionRepositoryFilter FilterClass: org.springframework.session.web.http.SessionRepositoryFilter
Filter Name: formContentFilter FilterClass: org.springframework.boot.web.servlet.filter.OrderedFormContentFilter
Filter Name: requestContextFilter FilterClass: org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter
Filter Name: springSecurityFilterChain FilterClass: org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1
	Ant [pattern='/user']
		org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@6e93b0e7
		org.springframework.security.web.context.SecurityContextPersistenceFilter@14be750c
		org.springframework.security.web.header.HeaderWriterFilter@5112b7
		org.springframework.security.web.csrf.CsrfFilter@48c5698
		org.springframework.security.web.authentication.logout.LogoutFilter@49190ed6
		org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3d033453
		org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@598657cd
		org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@5eeee124
		org.springframework.security.web.savedrequest.RequestCacheAwareFilter@15e08615
		org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7f93f4c
		org.springframework.security.web.authentication.AnonymousAuthenticationFilter@62cd562d
		org.springframework.security.web.session.SessionManagementFilter@40f1aa95
		org.springframework.security.web.access.ExceptionTranslationFilter@4efb13f1
		org.springframework.security.web.access.intercept.FilterSecurityInterceptor@49631cfb
	Ant [pattern='/headers']
		org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@e972ee1
		org.springframework.security.web.context.SecurityContextPersistenceFilter@b94703b
		org.springframework.security.web.header.HeaderWriterFilter@a9e8da1
		org.springframework.security.web.csrf.CsrfFilter@2037f12b
		org.springframework.security.web.authentication.logout.LogoutFilter@4abbe41c
		org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3ac0a14b
		org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@720a1fd0
		org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@5e24592e
		org.springframework.security.web.savedrequest.RequestCacheAwareFilter@33a47707
		org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4d290757
		org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6f0a4e30
		org.springframework.security.web.session.SessionManagementFilter@14b5752f
		org.springframework.security.web.access.ExceptionTranslationFilter@687a0e40
		org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1f45db49
	any request
		org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1b791dca
		org.springframework.security.web.context.SecurityContextPersistenceFilter@7cca31fc
		org.springframework.security.web.header.HeaderWriterFilter@273dec10
		org.springframework.security.web.csrf.CsrfFilter@2fc7fa6e
		org.springframework.security.web.authentication.logout.LogoutFilter@1d944fc0
		org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c3c453b
		org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@30cafd13
		org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@15ac02d5
		org.springframework.security.web.savedrequest.RequestCacheAwareFilter@619c3546
		org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@29090809
		org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2af9a5ef
		org.springframework.security.web.session.SessionManagementFilter@6cceb281
		org.springframework.security.web.access.ExceptionTranslationFilter@7f8c48f3
Filter Name: Tomcat WebSocket (JSR356) Filter FilterClass: org.apache.tomcat.websocket.server.WsFilter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment