Search code examples
javaspring-bootspring-securityspring-security-config

StackOverflowError with Unresolvable Circular Reference in AuthenticationManager Bean Definition


I'm encountering a StackOverflowError in my Spring Boot application. It appears that the issue is related to the definition of the AuthenticationManager bean. I use the AuthenticationManager bean in my AuthenticationService class and define it in my SecurityConfig class. However, it seems that this results in a circular dependency issue.

Here are the relevant code snippets:

package com.mehmetkaradana.java_api.service;

import com.mehmetkaradana.java_api.Repository.UserRepository;
import com.mehmetkaradana.java_api.model.User;
import com.mehmetkaradana.java_api.security.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AuthenticationService {

@Autowired
private UserRepository userRepository;

@Autowired
@Lazy
private PasswordEncoder passwordEncoder;

@Autowired
private JwtUtil jwtUtil;

@Autowired
@Lazy
private AuthenticationManager authenticationManager;

public String authenticate(String username, String password) {
    System.out.println("Attempting authentication for user: " + username);
    authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    return jwtUtil.generateToken(username);
}

public User saveUser(User user) {
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    return userRepository.save(user);
}

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userRepository.findByUsername(username).orElseThrow(() ->
            new UsernameNotFoundException("User not found with username: " + username));
}

}

And

package com.mehmetkaradana.java_api.config;

import com.mehmetkaradana.java_api.security.JwtRequestFilter;
import com.mehmetkaradana.java_api.service.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import     org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

private final AuthenticationService authenticationService;
private final JwtRequestFilter jwtRequestFilter;

@Autowired
public SecurityConfig(AuthenticationService authenticationService, JwtRequestFilter jwtRequestFilter) {
    this.authenticationService = authenticationService;
    this.jwtRequestFilter = jwtRequestFilter;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(requests -> requests
                    .requestMatchers("/register", "/login").permitAll()
                    .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
            .cors(withDefaults()); // Ensure CORS is configured if needed

    return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Bean
@Lazy
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}

}

Error Received:

java.lang.StackOverflowError
at java.base/java.lang.reflect.Method.invoke(Method.java:561) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354) ~    [spring-aop-6.1.11.jar:6.1.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.1.11.jar:6.1.11]

... Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.authentication.AuthenticationManager]: Factory method 'authenticationManager' threw exception with message: Error creating bean with name 'securityConfig': Requested bean is currently in creation: Is there an unresolvable circular reference? ...

Question: How can I resolve this issue? Despite using @Lazy annotations on AuthenticationManager and PasswordEncoder beans, I'm still encountering a circular dependency error. Any advice or suggestions on how to address this problem?


Solution

  • the reason for stackoverflow is this code:

    @Bean
    @Lazy
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
    

    If you look inside of AuthenticationConfiguration class you will see that AuthenticationConfiguration tries to get the bean of AuthenticationManager in order to provide it, for which Spring tried to instantiate it for which AuthenticationConfiguration tries to get it... you get the picture


    Here is how you can define authenticationManager instead:

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncode, UserDetailsService userDetailsService) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
                    http.getSharedObject(AuthenticationManagerBuilder.class);
    
        return authenticationManagerBuilder
                    .userDetailsService(userDetailsService)
                    .passwordEncoder(passwordEncoder)
                    .build();
    }