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

Debugging Spring Boot Security: Why You Keep Getting 401 or 403 (and How to Find the Real Cause)

Debugging Spring Security — Why You Keep Getting 401 or 403

If you’ve ever enabled Spring Boot Security and suddenly every request starts returning 401 Unauthorized or 403 Forbidden, you’re not alone.

What makes this frustrating is not the error itself — it’s that Spring Security often hides the real reason:

  • Is the token expired?
  • Is the token malformed?
  • Is a required claim missing?
  • Does the user lack the correct role?
  • Did the request even reach your controller?

This post walks through how to debug Spring Security properly, covering both:

  • Spring MVC (Servlet / Web)
  • Spring WebFlux (Reactive)

First: Understand 401 vs 403

Before debugging, it’s critical to understand what these errors actually mean in Spring Security.

401 Unauthorized

Usually means:

  • No token provided
  • Token is invalid or expired
  • Authentication failed

👉 Authentication problem

403 Forbidden

Usually means:

  • Authentication succeeded
  • User is logged in
  • But does not have permission to access the resource

👉 Authorization problem

Knowing which one you’re getting already narrows the search by half.


Step 1: Turn on Spring Security Debug Logs

Spring Security is very quiet by default.

Add this to application.yml:


logging:
  level:
    org.springframework.security: DEBUG

Or even more detailed:


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

This immediately shows:

  • Which filter rejected the request
  • Whether authentication was attempted
  • Whether authorization failed later

📌 This is the single most important step.


Step 2: Debug JWT Authentication (Servlet / Web)

Common Issue: Token Expired or Invalid

If you use JWT, authentication usually fails inside:

  • BearerTokenAuthenticationFilter
  • JwtAuthenticationProvider

Enable logs and look for messages like:

  • JWT expired
  • Invalid signature
  • Failed to decode JWT

If you have a custom decoder:


@Bean
JwtDecoder jwtDecoder() {
    return NimbusJwtDecoder
        .withJwkSetUri(jwkSetUri)
        .build();
}

Wrap decoding with logging:


try {
    return decoder.decode(token);
} catch (JwtException ex) {
    log.error("JWT decode failed", ex);
    throw ex;
}

This exposes the real root cause instead of just a blind 401.


Step 3: Missing or Incorrect Claims

One of the most common silent failures in Spring Security is missing or misconfigured claims.

Example:


.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admin/**").hasRole("ADMIN")
)

But your token contains:


{
  "roles": ["admin"]
}

Spring expects authorities like:


ROLE_ADMIN

Fix it explicitly using a JwtGrantedAuthoritiesConverter:


JwtGrantedAuthoritiesConverter converter =
    new JwtGrantedAuthoritiesConverter();

converter.setAuthoritiesClaimName("roles");
converter.setAuthorityPrefix("ROLE_");

Then wire it:


JwtAuthenticationConverter authConverter =
    new JwtAuthenticationConverter();

authConverter.setJwtGrantedAuthoritiesConverter(converter);

Without this, you’ll get a 403 with no clear explanation.


Step 4: Add a Custom AuthenticationEntryPoint (Servlet)

To expose authentication failures:


@Bean
AuthenticationEntryPoint authenticationEntryPoint() {
    return (request, response, authException) -> {
        log.error("Authentication failed", authException);
        response.sendError(
            HttpServletResponse.SC_UNAUTHORIZED,
            authException.getMessage()
        );
    };
}

This turns a silent 401 into a diagnosable error.


Reactive (WebFlux): Debugging Is Different

In Spring WebFlux, authentication failures happen inside the reactive chain — often without obvious errors.

Enable Reactive Security Logs


logging:
  level:
    org.springframework.security.web.server: DEBUG

Add a Reactive Authentication Entry Point


@Bean
ServerAuthenticationEntryPoint entryPoint() {
    return (exchange, ex) -> {
        log.error("Reactive auth failed", ex);
        exchange.getResponse()
                .setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    };
}

Inspect the Reactive Security Context

Remember: in WebFlux, the security context is not thread-local.


ReactiveSecurityContextHolder.getContext()
    .doOnNext(ctx ->
        log.info("User: {}", ctx.getAuthentication())
    )

If this never runs — authentication never succeeded.


Step 5: Check Filter Order (Servlet & Reactive)

Custom filters in the wrong order cause mysterious failures.

Servlet


.addFilterBefore(customFilter,
    UsernamePasswordAuthenticationFilter.class)

Reactive


http.addFilterAt(customFilter,
    SecurityWebFiltersOrder.AUTHENTICATION)

Wrong order = token never processed = endless 401s.


A Simple Debug Checklist

When stuck with 401 / 403:

  1. Enable Spring Security DEBUG logs
  2. Confirm token is received by the server
  3. Confirm JWT decoding succeeds
  4. Log claims and authorities
  5. Verify role prefixes (ROLE_)
  6. Check filter order
  7. Add custom entry points for visibility
  8. In WebFlux, verify reactive security context

Final Thoughts

Spring Security is powerful — but not transparent by default.

Most 401 / 403 issues are not security bugs, but missing visibility.

Once you:

  • Turn on logs
  • Add explicit handlers
  • Inspect claims and authorities

Security becomes predictable instead of painful.

If this post saved you time, feel free to support the blog — otherwise, happy debugging 🙂

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

Health Checks and Scaling Strategies for Next.js in Kubernetes