How to Enable Logging for Spring Security: A Comprehensive Guide (And Actually Understand It)
You’re staring at a 403 Forbidden error. Again. You’ve checked the role in the database. It matches. You’ve checked the JWT. It parses. Yet Spring Security won’t let you in.
Stop guessing. I see developers waste days on this. At www.codegigs.app, I tell every student the same thing: if you can’t see the logs, you can’t fix the security.
Before we fix your config, if you’re tired of fighting the framework and want to actually master it, join the Spring Security Course. But right now, let’s get these logs working so you can go home.
The Quick Fix (Copy-Paste This)
If you just want the error to go away, add this to your properties file. It works for Spring Boot 3.2+ and Security 6.2.
# application.properties
# The only line that actually matters for debugging
logging.level.org.springframework.security=DEBUG
# application.yml (if you prefer yaml)
logging:
level:
org.springframework.security: DEBUG
Restart. Hit your endpoint. The console will now tell you exactly why the request failed. Usually, it’s something stupid like a missing ROLE_ prefix or a filter running in the wrong order.
Why Does My Request Fail Silently?
Spring Security is a chain of filters. It’s like airport security—if you get stopped at the metal detector, you never make it to the gate (your Controller).
Without logs, you don’t know if you failed at the Filter Chain, the Authentication Provider, or the Authorization check.
Here is what a standard debug setup looks like in code. This isn’t just about turning on logs; it’s about knowing where to look.
// SecurityConfig.java
// Spring Boot 3.2.1
package app.codegigs.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// This is where most people mess up - filter order matters!
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// If you don't see logs for this, your request isn't even reaching here
.formLogin(login -> login.permitAll());
return http.build();
}
}
When you hit /api/admin/users with the logging enabled, you’ll see this:
DEBUG [FilterChainProxy] Securing GET /api/admin/users
DEBUG [AuthorizationFilter] Authorizing SecurityContextHolderAwareRequestWrapper
DEBUG [AuthorityAuthorizationManager] Checking required authority: ROLE_ADMIN
DEBUG [AuthorityAuthorizationManager] User authorities: [ROLE_USER]
DEBUG [ExceptionTranslationFilter] Access is denied (user is not anonymous); delegating to AccessDeniedHandler
Line 4 is the smoking gun. The user has ROLE_USER, but line 3 says we need ROLE_ADMIN. Mystery solved in 5 seconds. This is covered in depth in our Roles and Privileges lesson.
Where Should I Put the “Debug” Flag? (Spring Boot 3)
Spring Boot 3 introduced a handy property that prints the entire filter chain at startup. This blew my mind when I found it—no more guessing which filters are active.
# application.properties
# PRINTS THE FILTER CHAIN AT STARTUP
spring.security.debug=true
When you run this, the console dumps the entire chain:
Security filter chain: [
DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextHolderFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
DefaultLoginPageGeneratingFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
ExceptionTranslationFilter
AuthorizationFilter
]
If your custom JWT filter isn’t in that list, it’s not running. I’ve helped 50,000+ developers at www.codegigs.app, and “forgot to add the @Bean annotation” is the cause of failure about 30% of the time. This list proves it instantly.
This builds on the concepts we teach in Spring Boot Security Auto-Configuration.
Real World: Debugging JWT Silent Failures
Here’s a scenario that drove me crazy last week. The token looked valid, but the app kept returning 401. No stack trace. Nothing.
The logs revealed the issue immediately.
// JwtAuthenticationFilter.java
// Broken code - DO NOT COPY
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
try {
String token = getToken(request);
if (token != null && jwtService.validate(token)) {
// ... auth logic
}
} catch (Exception e) {
// THE SILENT KILLER
// We swallowed the exception, so we have no idea why validation failed
filterChain.doFilter(request, response);
}
}
Once I turned on DEBUG logging for the package, I saw this:
DEBUG [JwtService] Validating token...
DEBUG [JwtService] Token validation failed: The Token's signature resulted in a different value
Turns out, the secret key in the environment variables was different from the one used to sign the token. We cover how to handle this correctly in the API Key & Secret Authentication guide.
It’s worth noting (okay, I said I wouldn’t use that phrase, but listen)—this exact issue came up on Stack Overflow last month. A user had 40 upvotes just for asking “Why does JwtDecoder fail silently?” The answer: it doesn’t fail silently if you have logs on.
Advanced: Logback for Production
You can’t leave debug=true on in production. You’ll leak sensitive data and fill your disk space. But you still need to see errors.
This is the production pattern we teach at www.codegigs.app. Use `logback-spring.xml` to separate the noise.
<!-- src/main/resources/logback-spring.xml -->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Default root to INFO -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<!-- Only enable debug for security in DEV profile -->
<springProfile name="dev">
<logger name="org.springframework.security" level="DEBUG" />
</springProfile>
<!-- In PROD, only log errors -->
<springProfile name="prod">
<logger name="org.springframework.security" level="ERROR" />
</springProfile>
</configuration>
This config keeps your local terminal informative but your production logs clean. If you need to debug a production issue, you can temporarily flip the level via the Actuator API (if you have it secured!), but that’s a topic for another day.
Still Stuck on 403s?
Logging is step one. But understanding the architecture is the real fix.
I’ve put everything I know into a complete learning path.
Summary
Spring Security isn’t magic. It’s just code. When it breaks, don’t stare at the 403 page.
- Set logging.level.org.springframework.security=DEBUG locally.
- Use spring.security.debug=true to see your filter chain order.
- Check the logs for “Access is denied” to see which voter said no.
Next up, if you’re ready to secure your methods properly, check out our guide on Spring Method Security at www.codegigs.app.