Senior 6 min · May 23, 2026

Spring Security 6.x: The AuthenticationProvider That Burned Our Q3 Release

Spring Security interview questions for senior devs.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • 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.
✦ Definition~90s read
What is Spring Security 6.x?

Spring Security is the de facto security framework for Java applications. In Spring Boot 3.x, it's been overhauled. The old classes are deprecated. The new component-based model uses SecurityFilterChain beans. Authentication happens through a chain of AuthenticationProvider instances.

Spring Security is like a bouncer at a club.

Authorization is handled via method security annotations or request matchers. It's not a library. It's a collection of filters that modify the request and response objects. Each filter has a single responsibility. Authentication filters, exception translation filters, authorization filters.

They chain together. One breaks the chain, the whole thing fails silently. The biggest shift in 6.x is the removal of the old configuration adapter. You now declare beans. The framework wires them. This is better. It gives you more control. But it also introduces new failure modes.

Especially around filter ordering.

Plain-English First

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.

Production Trap:
Forgetting @Order causes non-deterministic filter chain selection. You get 401s that come and go. This is a hard-to-reproduce bug that wastes hours.
Production Insight
In production, always define one SecurityFilterChain per path pattern. Never rely on a single catch-all chain for a complex app.
Key Takeaway
SecurityFilterChain beans must have explicit @Order. Most specific routes first. Least specific last.

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.

Senior Shortcut:
Log the provider chain at startup with a simple @PostConstruct in a configuration class. Call ProviderManager.getProviders() if accessible, or inject AuthenticationManager and log its class name.
Production Insight
The provider chain is a list. First match wins. Treat it like a chain of responsibility with no fallback.
Key Takeaway
AuthenticationProvider ordering is critical. Always order most specific (like JWT) before generic (like password). Log the chain on startup.

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.

Never Do This:
Do not mix @EnableGlobalMethodSecurity with @EnableMethodSecurity. The old annotation creates a separate interceptor chain that conflicts with the new one. Pick one. The new one.
Production Insight
If @PreAuthorize seems to not fire, check which @Enable annotation you imported. This is the single most common upgrade bug.
Key Takeaway
@EnableMethodSecurity replaces @EnableGlobalMethodSecurity. Mixing them breaks method security silently. Remove the old annotation entirely.

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.

Interview Gold:
Interviewers ask: 'When should you disable CSRF?' Answer: When your API is stateless and uses token-based authentication. If the server doesn't set a session cookie, CSRF is irrelevant.
Production Insight
CORS and CSRF are separate filters that both affect POST requests. Debug CORS first (preflight), then CSRF (actual request).
Key Takeaway
For stateless REST APIs in Spring Security 6.x, always disable CSRF and configure CORS explicitly. They are separate concerns.

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.

Production Trap:
Clock skew on the authorization server causes silent 401s. The error says 'expired' but the token is valid. Always configure a clock skew tolerance in JwtDecoder.
Production Insight
Error responses from OAuth2 resource server are deliberately vague. Always keep TRACE logging enabled in dev/staging to see the real error message.
Key Takeaway
JWT validation failures in Spring Security 6.x are silent. Enable OAuth2 TRACE logging. Configure clock skew tolerance. Verify JwtDecoder bean exists.

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.

Senior Shortcut:
Use @WithMockUser instead of real authentication in unit tests. It decouples the test from the provider chain and network dependencies.
Production Insight
Test SecurityFilterChain beans override production ones. Use profiles, not @TestConfiguration, to avoid this trap.
Key Takeaway
Never define SecurityFilterChain in @TestConfiguration. Use @ActiveProfiles("test") and per-profile security config instead.
● Production incidentPOST-MORTEMseverity: high

The AuthenticationProvider That Ate Our OAuth2 Login

Symptom
All authenticated endpoints returned 401 Access Denied. OAuth2 login flows completed successfully but subsequent API calls failed. No unusual log errors — just generic security warnings.
Assumption
The team thought the JWT signing key had rotated or the OAuth2 provider's public certificate was expired. They regenerated keys. No effect.
Root cause
The SecurityFilterChain configuration had the DaoAuthenticationProvider listed before the OAuth2AuthenticationProvider. Every request was being authenticated against the password-based provider first, which rejected all JWT tokens. Spring Security's provider chain stops at the first successful authentication. The OAuth2 provider never got a chance.
Fix
Reordered the AuthenticationProvider beans in the SecurityFilterChain configuration. Moved the OAuth2AuthenticationProvider declaration before the DaoAuthenticationProvider. Added @Order annotations to ensure consistent ordering across deployments. Also added a logging filter that prints the provider chain on startup.
Key lesson
  • AuthenticationProvider ordering is deterministic but invisible.
  • Always log the resolved provider chain during initialization.
  • Never assume the default ordering matches your intent.
Production debug guideSymptom → root cause → fix for the failures that actually happen4 entries
Symptom · 01
401 Unauthorized on all authenticated endpoints after OAuth2 login
Fix
Check the AuthenticationProvider chain. Add a breakpoint or log statement in AbstractAuthenticationManager's authenticate method. The provider chain is a list, and iteration stops at the first successful match. If your OAuth2 provider is later in the list, it never runs. Reorder providers in SecurityFilterChain. Put the most specific providers first.
Symptom · 02
Method-level @PreAuthorize annotations not working in Spring Boot 3.x
Fix
Verify you are using @EnableMethodSecurity, not @EnableGlobalMethodSecurity. The old annotation is deprecated. It still compiles but doesn't wire into the new AOP infrastructure. Check the application context for the MethodSecurityInterceptor bean. If it's missing, you're using the wrong annotation.
Symptom · 03
POST requests returning 403 Forbidden with no CSRF token
Fix
CSRF protection is enabled by default in Spring Security 6.x. For REST APIs that don't use session-based auth, disable it with http.csrf(AbstractHttpConfigurer::disable). But do it consciously. If you need CSRF, configure the token repository to expose the token via a cookie or header endpoint.
Symptom · 04
OAuth2 Resource Server returns 401 but JWT is valid
Fix
Check the NimbusJwtDecoder configuration. In Spring Security 6.x, JWT validation failures don't throw exceptions. They return a 401 with a generic 'Invalid token' message. The real error is in the debug logs. Enable TRACE logging for org.springframework.security.oauth2. Check the JWK set URI is accessible and the public key matches.
★ Debug Cheat SheetCommands for fast diagnosis in production
All users get 401 after login
Immediate action
Check AuthenticationProvider ordering
Commands
kubectl logs pod/your-service-pod | grep 'ProviderManager.authenticate'
curl -v -X POST https://your-api.com/api/test-auth -H 'Authorization: Bearer <token>'
Fix now
In SecurityFilterChain config, move OAuth2AuthenticationProvider before DaoAuthenticationProvider. Add @Order annotation.
Method security annotations ignored+
Immediate action
Check which annotation is imported
Commands
grep -r 'EnableGlobalMethodSecurity' src/main/java/
actuator/beans | grep MethodSecurityInterceptor
Fix now
Replace @EnableGlobalMethodSecurity with @EnableMethodSecurity in your configuration class.
POST requests getting 403 in REST API+
Immediate action
Check if CSRF is enabled
Commands
curl -v -X POST https://your-api.com/api/orders -H 'Content-Type: application/json' -d '{}' | grep 403
kubectl exec -it pod/your-service-pod -- cat /tmp/spring.log | grep CSRF
Fix now
Add http.csrf(AbstractHttpConfigurer::disable) to your SecurityFilterChain if it's a stateless REST API.
Spring Security 5.x vs 6.x: What Changed
FeatureSpring Security 5.xSpring Security 6.x
ConfigurationWebSecurityConfigurerAdapter subclassSecurityFilterChain @Bean
Method Security@EnableGlobalMethodSecurity@EnableMethodSecurity
CSRF DefaultDisabled by defaultEnabled by default
AuthenticationProviderOrdered by declarationOrdered by @Order or bean dependency
JWT Validation ErrorThrows exceptionReturns 401 silently
Filter OrderingFixed by adapter classMust specify @Order explicitly

Key takeaways

1
SecurityFilterChain beans must have explicit @Order. Most specific routes first. This prevents non-deterministic filter selection.
2
AuthenticationProvider chain ordering is invisible. Log it on startup. Put JWT providers before password providers.
3
Always disable CSRF for stateless REST APIs. It's enabled by default in 6.x and blocks POST requests.
4
@EnableMethodSecurity replaces @EnableGlobalMethodSecurity. Mixing them silently breaks all role checks.
5
JWT validation errors are hidden from HTTP responses. Enable TRACE logging for org.springframework.security.oauth2 in staging environments.

Common mistakes to avoid

5 patterns
×

Using WebSecurityConfigurerAdapter in Spring Boot 3.x

Symptom
Compiles but security is missing. All endpoints accessible without authentication.
Fix
Replace with SecurityFilterChain @Bean definitions. Remove the adapter class entirely.
×

Forgetting @Order on multiple SecurityFilterChain beans

Symptom
Intermittent 401s. Different on each restart. Hard to reproduce.
Fix
Add @Order(1), @Order(2), etc. Start with most specific path pattern.
×

Mixing @EnableGlobalMethodSecurity with @EnableMethodSecurity

Symptom
Some @PreAuthorize annotations work. Most don't. No error message.
Fix
Keep only @EnableMethodSecurity. Remove the old annotation and its import.
×

Not disabling CSRF for stateless REST API

Symptom
All POST/PUT/DELETE requests return 403 Forbidden.
Fix
Add .csrf(AbstractHttpConfigurer::disable) to the SecurityFilterChain.
×

Assuming JWT validation errors appear in response body

Symptom
401 with no details. Unable to diagnose why JWT is rejected.
Fix
Enable TRACE logging for org.springframework.security.oauth2 to see real error.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What happens if you define two SecurityFilterChain beans in Spring Secur...
Q02JUNIOR
Why does @PreAuthorize stop working after upgrading to Spring Boot 3.x?
Q03SENIOR
Your OAuth2 API returns 401 on POST requests. The JWT is valid. What do ...
Q04SENIOR
How does the AuthenticationProvider chain work in Spring Security 6.x?
Q05SENIOR
What is the difference between CORS and CSRF in Spring Security?
Q06JUNIOR
How do you debug a '401 with no message' in a Spring Security 6.x OAuth2...
Q07SENIOR
What's the correct way to test Spring Security method-level annotations?
Q08SENIOR
Explain how Spring Security 6.x handles OAuth2 resource server JWT valid...
Q01 of 08SENIOR

What happens if you define two SecurityFilterChain beans in Spring Security 6.x without @Order?

ANSWER
Spring picks one arbitrarily. It uses bean discovery order, which is non-deterministic across restarts. This causes intermittent security failures. Always annotate with @Order, starting from the most specific path.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Why is my POST request getting 403 Forbidden in Spring Security 6.x?
02
Does @EnableGlobalMethodSecurity still work in Spring Boot 3.x?
03
How do I debug a 401 from Spring Security OAuth2 resource server?
04
Can I have multiple SecurityFilterChain beans in one application?
05
What's the best way to test security in Spring Boot 3.x?
🔥

That's Interview. Mark it forged?

6 min read · try the examples if you haven't

Previous
Microservices Interview Questions
3 / 4 · Interview
Next
Design Patterns Interview Questions for Java