How to log Spring Security authorization decisions properly (without noisy logs
How to log Spring Security authorization decisions properly (without noisy logs)
One of the most frustrating things about Spring Security is this:
“I know I got a 403 — but I have no idea why.”
The problem is usually not Spring Security itself.
The problem is that authorization decisions are rarely logged in a useful way.
In this post, I’ll show how to log:
- Which authorization rule was evaluated
- What authorities were present
- Why access was denied
All without turning your logs into unreadable noise.
Why default logs are not enough
Most teams start with:
logging.level.org.springframework.security=DEBUG
This helps — but only to a point.
In real systems, DEBUG logs often:
- Flood your logs
- Hide the actual decision
- Are too late in the request lifecycle
What we really want is focused, decision-level logging.
What we actually want to know
When access is denied, these four answers matter:
- Who is the user?
- What authorities do they have?
- Which rule was evaluated?
- Why did it fail?
Everything else is secondary.
Approach 1: Log authorities right before authorization
The simplest and most effective technique is to log the authentication object just before authorization happens.
For example, inside a custom filter or authorization manager:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
log.info("User={}, authorities={}",
auth.getName(),
auth.getAuthorities());
}
This immediately answers:
- Was authentication successful?
- Were any authorities extracted?
If authorities are empty, you already know the problem.
Approach 2: Custom AccessDeniedHandler (Servlet)
For Spring MVC (Servlet-based apps), a custom
AccessDeniedHandler gives you a clean logging point.
public class LoggingAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException ex
) {
Authentication auth =
SecurityContextHolder.getContext().getAuthentication();
log.warn(
"Access denied. path={}, user={}, authorities={}",
request.getRequestURI(),
auth != null ? auth.getName() : "anonymous",
auth != null ? auth.getAuthorities() : "none"
);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
This produces one clean log line per denial.
No noise. No guessing.
Approach 3: Authorization logging in WebFlux
Reactive security is different.
There is:
- No thread-local context
- No global handler you can tap into easily
The correct place to log is inside the reactive chain.
For example, in a custom ReactiveAuthorizationManager:
return Mono.deferContextual(ctx -> {
Authentication auth = ctx.get(Authentication.class);
log.info(
"Authorizing user={}, authorities={}",
auth.getName(),
auth.getAuthorities()
);
return decision;
});
Logging outside the reactive pipeline often logs nothing.
Approach 4: Log matcher evaluation (the missing piece)
Many 403 issues are caused by the wrong matcher being applied.
Enable this temporarily:
logging.level.org.springframework.security.web.access=DEBUG
This shows:
- Which matcher matched the request
- Which rule was evaluated
Once the issue is resolved, turn it back off.
What NOT to log
Avoid logging:
- Entire JWT tokens
- Raw request headers
- Every filter in the chain
This adds risk and noise without improving clarity.
A practical logging strategy
In real systems, I recommend:
- INFO logs for access denied events
- DEBUG logs only when actively debugging
- One log line per authorization decision
This keeps logs:
- Readable
- Actionable
- Safe
Final thoughts
Spring Security does not hide decisions — it just doesn’t log them by default.
Once you log the right decision points, 403 errors stop being mysterious.
If you’re guessing, you’re logging in the wrong place.
Next in the series:
How to debug Spring Security filter chains step by step.
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
Post a Comment