A New Collection of Thoughtful Learning Apps — Now Available on iOS & Android

Image
I’m excited to share a set of mobile apps I’ve recently completed and published on both the Google Play Store and the Apple App Store. These apps are designed with a simple goal in mind: to make meaningful, structured content more accessible, whether you’re studying theology or improving your English vocabulary. 📱 Now Available on Both Platforms All apps are live and available for download: Google Play Developer Page: https://play.google.com/store/apps/dev?id=5835943159853189043 Apple App Store Developer Page: https://apps.apple.com/ca/developer/q-z-l-corp/id1888794100 📖 Theology & Confession Study Apps For those interested in Reformed theology and classical Christian teachings, I’ve developed a series of apps that present foundational texts in a clean, focused reading format: The Belgic Confession Canons of Dort Heidelberg Catechism Westminster Shorter Catechism Each app is designed to provide a distraction-free experience, making it easier to read, reflect, and revisit these im...

How to debug Spring Security filter chains step by step

How to debug Spring Security filter chains step by step

When Spring Security blocks a request, it usually happens long before your controller is reached.

At that point, many developers are stuck asking:

“Which filter rejected my request — and why?”

This post shows a practical, step-by-step approach to debug Spring Security filter chains, without reading the entire framework source code.


Why filter chains are so hard to debug

Spring Security uses a chain of filters, each responsible for one concern:

  • Request matching
  • Authentication
  • Authorization
  • Exception handling

Once a filter rejects a request, the chain stops.

If you don’t know which filter made the decision, debugging becomes guesswork.


Step 1: Confirm which SecurityFilterChain is used

The first thing to verify is whether the request is handled by the expected security chain.

Enable this log:

logging.level.org.springframework.security.web.FilterChainProxy=DEBUG

You will see output like:



Securing GET /api/admin/users

Using SecurityFilterChain [RequestMatcher=any request]

If the wrong chain is selected, everything that follows is irrelevant.

Many 401 / 403 issues start here.


Step 2: Identify the filter where the chain stops

Next, enable:

logging.level.org.springframework.security=DEBUG

Now each filter will log when it processes the request.

Look for the last filter that logs before the response is returned.

That filter is where the request was rejected.


Step 3: Know the most common “stop filters”

In real-world systems, most failures happen in just a few filters:

  • BearerTokenAuthenticationFilter — JWT not parsed or rejected
  • AnonymousAuthenticationFilter — request treated as anonymous
  • ExceptionTranslationFilter — converts exceptions to 401 / 403
  • AuthorizationFilter — access denied

Once you know which filter stopped the chain, the cause is usually obvious.


Step 4: Distinguish authentication vs authorization failures

This distinction is critical:

  • If the chain stops before authentication → 401
  • If it stops after authentication → 403

Ask this question:

Did Spring ever create an Authentication object?

If not, focus on token parsing and filters.

If yes, focus on roles, authorities, and matchers.


Step 5: Log SecurityContext at key points

Strategically logging the security context saves hours of debugging.

Right after authentication:



Authentication auth =

    SecurityContextHolder.getContext().getAuthentication();

log.info("Authenticated user={}, authorities={}",

    auth != null ? auth.getName() : "none",

    auth != null ? auth.getAuthorities() : "none");

If this log never appears, authentication never succeeded.


Step 6: Watch for matcher-related surprises

Security rules are evaluated in order.

A common mistake:



.requestMatchers("/api/**").authenticated()

.requestMatchers("/api/admin/**").hasRole("ADMIN")

The first rule matches everything — the second rule is never reached.

Debug logs will reveal this immediately.


Step 7: Reactive (WebFlux) filter chains behave differently

In WebFlux:

  • There is no thread-local context
  • Logging outside the reactive chain often shows nothing

Filters may appear to run, but authentication context is lost downstream.

Always log inside the reactive pipeline.


A repeatable debugging checklist

When a request fails, walk through this checklist:

  1. Which SecurityFilterChain handled the request?
  2. Which filter was last executed?
  3. Was authentication created?
  4. Which matcher and rule were applied?

If you can answer all four, the bug is usually trivial.


Final thoughts

Spring Security is complex — but it is not random.

Every 401 or 403 comes from a specific filter, in a specific chain, for a specific reason.

Once you learn how to observe the filter chain, debugging becomes systematic instead of frustrating.


Series complete:
This post concludes the Spring Security 401 / 403 debugging series.

Part of the Spring Boot SSO Series

❤️ Support This Blog


If this post helped you, you can support my writing with a small donation. Thank you for reading.


Comments

Popular Posts

2026 Begins: Choosing to Stay on the Path as a Blogger

A New Collection of Thoughtful Learning Apps — Now Available on iOS & Android