Spring Security: Authentication returns anonynmous user after login. How to fix it?
After a successful login in a Spring Boot application using Spring Security, the SecurityContext
still contains an anonymous user instead of the authenticated user.
Everytime I try to call SecurityContextHolder.getContext().getAuthentication().getPrincipal()
, it returns anonymousUser
or null
. Same thing when I try with the annotation @AuthenticationPrincipal
Also the endpoints with required authentication return 403 Forbidden
. How can I fix this?
Solution
You can try the following steps to fix this:
1. Ensure SecurityContextPersistenceFilter is applied
Spring Security maintains authentication state using SecurityContextPersistenceFilter
. If this filter is missing or overridden incorrectly, the user session won't persist.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/secure/**").authenticated()
.anyRequest().permitAll()
)
.formLogin(withDefaults())
.securityContext(security -> security.securityContextRepository(new HttpSessionSecurityContextRepository()));
return http.build();
}
This ensures that authentication is stored in the session and persists across requests.
2. Ensure the Custom Authentication Provider is Setting Authentication
If you're using a custom authentication provider, ensure it properly sets authentication:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails user = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
Ensure that the authentication object is set in SecurityContextHolder
:
SecurityContextHolder.getContext().setAuthentication(authentication);
3. Verify Filters Execution Order
Spring Security applies filters in a specific order. If a custom filter runs before authentication, the SecurityContext
might not update properly.
Check logs to ensure that authentication happens before the request reaches secured endpoints.
Alternative #1
I've debugged this exact issue in production applications, and one thing that often gets overlooked is the session management configuration. Sometimes the authentication is working, but the session isn't being maintained properly.
Check your application.properties
or application.yml
:
spring:
session:
store-type: redis # or jdbc, none
timeout: 30m
security:
session:
creation-policy: if-required
fixation: migrate-session
Also, make sure you're not accidentally clearing the session somewhere in your code:
@RestController
public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// Don't do this - it clears the session!
// request.getSession().invalidate();
// Instead, let Spring Security handle session management
return ResponseEntity.ok("Login successful");
}
}
I've also seen this happen when using JWT tokens without proper session management. If you're using JWT, make sure you have a proper JwtAuthenticationFilter
that sets the authentication in the context.
Alternative #2
Another common cause I've encountered is CORS configuration interfering with authentication. If you're making cross-origin requests, the browser might not send cookies/session data.
Check your CORS configuration:
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true); // This is crucial!
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
The key is setAllowCredentials(true)
- without this, the browser won't send authentication cookies.
Also, check if you're using CSRF protection incorrectly:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**") // Disable CSRF for API endpoints
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").authenticated()
);
return http.build();
}
CSRF tokens can interfere with authentication if not configured properly.
Alternative #3
If you're using custom authentication or OAuth2, the issue might be in how you're handling the authentication success. I've seen this happen with custom AuthenticationSuccessHandler
.
Here's a proper implementation:
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// Make sure the authentication is set in the context
SecurityContextHolder.getContext().setAuthentication(authentication);
// If using session, ensure it's saved
HttpSession session = request.getSession();
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
// Your custom logic here
response.setStatus(HttpStatus.OK.value());
response.getWriter().write("{\"message\":\"Authentication successful\"}");
}
}
Also, check if you have any custom filters that might be clearing the authentication:
@Component
public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Don't do this - it clears authentication!
// SecurityContextHolder.clearContext();
chain.doFilter(request, response);
}
}
The authentication context should persist throughout the request lifecycle.