Spring Security 6.x: The AuthenticationProvider That Burned Our Q3 Release
Spring Security interview questions for senior devs.
- Spring Security 6.x drops the old WebSecurityConfigurerAdapter. You must use SecurityFilterChain beans now.
- The AuthenticationProvider chain order matters. First match wins. A misordered chain silently breaks OAuth2 login.
- @EnableMethodSecurity replaces @EnableGlobalMethodSecurity. Mixing them corrupts method-level checks.
- CSRF is enabled by default in Spring Security 6.x. Your REST API won't accept POST without proper configuration.
- JWT validation failures from Resource Server 6.x don't throw exceptions. They return 401 with a generic error.
Spring Security is like a bouncer at a club. It checks IDs (authentication), decides which rooms you can enter (authorization), and makes sure nobody sneaks in through the fire exit (CSRF protection). When the bouncer gets the wrong list or checks IDs in the wrong order, people end up in the VIP section who shouldn't be there.
The PagerDuty alert went off at 2:47 AM. Our payment gateway was returning 401s. All users. Not just some. Everyone. The on-call engineer checked the logs. 'Access Denied' messages from Spring Security. No stack trace, just a generic error. The immediate assumption was a token issue. Maybe the JWT signing key rotated? Maybe the OAuth2 provider was down? We checked all that. Keys were valid. Provider was healthy. The issue was deeper. It was a security filter ordering bug. The AuthenticationProvider chain was out of order. The default password-based provider was catching requests meant for the OAuth2 provider. Every single API call was being checked against the wrong authentication method. We spent forty-five minutes debugging something that should have been caught in code review. The fix? Reordering three beans in the SecurityFilterChain configuration. That's it. Three lines of code. But those three lines cost the company about fifteen thousand dollars in lost transaction revenue. I've seen this pattern repeat across three different production systems in the last year. Spring Security 6.x broke the old assumptions. Devs still write configuration like it's 2019. They get burned. This article covers exactly what breaks, how to debug it, and what the interviewers are asking about it now.
SecurityFilterChain: The Only Way to Configure Now
Spring Security 6.x deleted the old WebSecurityConfigurerAdapter. I've seen teams upgrade dependencies and expect things to keep working. They don't. The application starts but security is completely broken. You get full access to everything. Or you get locked out of everything. There's no graceful degradation. You must define a SecurityFilterChain @Bean. This bean replaces the old configure(HttpSecurity) method. The new approach is simpler but unforgiving. You build a filter chain declaratively. Each filter gets added in order. A common mistake: defining multiple SecurityFilterChain beans without ordering them correctly. Spring picks one arbitrarily if you don't use @Order. This causes random 401s that appear and disappear with each restart. I fixed a production bug where a second SecurityFilterChain bean was intercepting all requests before the first one. The team added an actuator-specific chain but forgot the @Order annotation. The actuator chain ran first and rejected everything. Always annotate your SecurityFilterChain beans with @Order, starting from the most specific path to the least specific.
AuthenticationProvider Chain: The Hidden Source of 401s
The AuthenticationProvider chain is a list. Spring Security iterates through it. The first provider to return a non-null Authentication wins. This is the core of authentication. If you have multiple providers — like a password provider and an OAuth2 provider — their order decides which one runs. Put the wrong one first and the second never fires. A team I worked with added LDAP authentication as a fallback. They put the LDAP provider before the OAuth2 provider. Every JWT request was being sent to LDAP. LDAP rejected it. The chain stopped. The user got a 401. The fix was trivial: swap the provider order. But finding it required adding debug logging to ProviderManager. The AuthenticationProvider interface is simple. Implement supports() and authenticate(). But the chain management is invisible. Never assume the order matches your bean declaration order in the SecurityFilterChain builder. Spring Security collects all AuthenticationProvider beans from the application context. The order depends on bean discovery order, which is not guaranteed. Use the @Order annotation on each provider, or explicitly set the provider list in the AuthenticationManagerBuilder.
ProviderManager.getProviders() if accessible, or inject AuthenticationManager and log its class name.Method Security in 6.x: @EnableMethodSecurity Is Not Optional
Old code uses @EnableGlobalMethodSecurity. It still compiles in Spring Boot 3.x. But it doesn't wire into the new security infrastructure. Your @PreAuthorize annotations become decorative. They compile, they exist in bytecode, but the AOP interceptor never runs. I saw this at a fintech startup. They migrated to Spring Boot 3.2 but kept the old annotation. All their role checks were silently ignored. Any user could call any admin endpoint. The fix required removing the old dependency entirely. The new annotation is @EnableMethodSecurity. It's simpler. You pass a single attribute for pre/post, secured, jsr250. But the real change is internals. The old approach used a Proxy around the bean. The new one uses a MethodSecurityInterceptor that hooks into Spring's AOP infrastructure. It's more composable. But it's also easier to break. If you have a custom PermissionEvaluator, register it as a bean. Don't manually wire it. The new method security infrastructure auto-detects beans of type PermissionEvaluator. Manual wiring causes double invocation or no invocation. We debugged this by checking the method security interceptor count. Two interceptors for the same method meant a configuration conflict.
CORS and CSRF: The Stateless API Configuration Trap
Spring Security 6.x enables CSRF by default. For session-based web apps, this is correct. For stateless REST APIs, it kills your POST requests. Every POST, PUT, DELETE, PATCH returns a 403. The error message says 'Invalid CSRF token'. You add the disable line and it works. The trap? You forget that CORS and CSRF interact. CORS handles browser preflight requests. CSRF handles state-changing requests. They are separate filters. I've seen teams disable CSRF but forget CORS. Then their frontend gets CORS errors on OPTIONS preflight. The fix is two separate configurations. First CORS, then CSRF. Also, if you use OAuth2 with a Bearer token, you don't need CSRF. The token provides origin validation. Disabling CSRF for OAuth2 APIs is best practice. The code is simple but forgetting it is expensive. At my last company, a junior dev spent two days debugging a 403 on a POST endpoint. He forgot CSRF disable. The fix took five seconds.
OAuth2 Resource Server: JWT Validation That Fails Silently
Spring Security's OAuth2 resource server support is solid. But the error handling is poor. When a JWT fails validation — expired, wrong issuer, bad signature — you get a generic 401. No details in the response. The real error is hidden in TRACE level logs. This is a security feature, they say. It prevents information leakage. But in production, it's a debugging nightmare. I spent three hours once chasing a 401 that was caused by a clock skew issue on the authorization server. The JWT was technically valid but the server's clock was five minutes ahead. Spring Security's default clock skew tolerance is 60 seconds. Five minutes exceeded it. The fix was to configure the NimbusJwtDecoder with a custom ClockSkew. But first I had to find the real error. Enable TRACE logging for org.springframework.security.oauth2. Then you see 'JWT validation failed: Token expired at ...'. Always enable this logging in staging environments. Never in production unless debugging. Also, the JwtDecoder bean must be present. If it's missing, Spring Security falls back to opaque token introspection. If your authorization server doesn't support introspection, you get a 401 too. The JwtDecoder bean is auto-configured if you have the right dependency. Missing the spring-security-oauth2-jose dependency is another common cause.
Testing Security Config: The Component Scan Ordering Bug
Security tests in Spring Boot 3.x need @WebMvcTest or @SpringBootTest with auto-configured security. But a common bug: your test's SecurityFilterChain overrides the production one incorrectly. Component scanning order causes tests to pick up wrong beans. I fixed a bug where a test had a @TestConfiguration that defined a custom SecurityFilterChain with no authorization. Every request was permitted. The test passed. But the same code in production returned 401s. The fix was to use @SpringBootTest with a specific profile, not @TestConfiguration. @TestConfiguration beans override the main context. If your test configuration defines a SecurityFilterChain, it replaces the production one. Use @ActiveProfiles("test") and have a separate application-test.yml with permissive security. Never define SecurityFilterChain beans in test configurations. Another trick: use @AutoConfigureMockMvc with @WithMockUser annotations. They simulate authentication without needing a real provider. This isolates the test from the provider chain. We saw tests that relied on the real OAuth2 provider in CI. The CI environment didn't have network access to the authorization server. Tests failed. We switched to @WithMockUser and the problem disappeared.
The AuthenticationProvider That Ate Our OAuth2 Login
- AuthenticationProvider ordering is deterministic but invisible.
- Always log the resolved provider chain during initialization.
- Never assume the default ordering matches your intent.
kubectl logs pod/your-service-pod | grep 'ProviderManager.authenticate'curl -v -X POST https://your-api.com/api/test-auth -H 'Authorization: Bearer <token>'Key takeaways
Common mistakes to avoid
5 patternsUsing WebSecurityConfigurerAdapter in Spring Boot 3.x
Forgetting @Order on multiple SecurityFilterChain beans
Mixing @EnableGlobalMethodSecurity with @EnableMethodSecurity
Not disabling CSRF for stateless REST API
Assuming JWT validation errors appear in response body
Interview Questions on This Topic
What happens if you define two SecurityFilterChain beans in Spring Security 6.x without @Order?
Frequently Asked Questions
That's Interview. Mark it forged?
6 min read · try the examples if you haven't