prity sinha
prity sinha

Reputation: 241

Relying upon circular reference is discouraged and they are prohibited, by default, in spring boot application

I am getting below error message when I am running my spring boot application.

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  securityConfiguration (field private com.prity.springbootdemo1.service.UserService com.prity.springbootdemo1.config.SecurityConfiguration.userService)
↑     ↓
|  userServiceImpl (field private org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder com.prity.springbootdemo1.service.UserServiceImpl.passwordEncoder)
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

Upvotes: 24

Views: 91044

Answers (8)

Rostyslav Mazepa
Rostyslav Mazepa

Reputation: 81

Create a new service:

@Service

public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private IUserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    User user = userRepository.findByUsername(username);

    if(user != null) {
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                user.isActive(),
                true,
                true,
                true,
                AuthorityUtils.createAuthorityList(user.getRoles().toString())
        );
    }
    else {
        throw new UsernameNotFoundException("No user with "
                + "the name " + username + "was found in the database");
    }
}

}

Upvotes: 0

Ismael Valgy
Ismael Valgy

Reputation: 41

Problem Explanation (note: you don't really need to read this)

When dealing with circular dependencies in Spring, it's akin to having two friends, A and B, who rely on each other excessively. To resolve this, we can use the @Lazy annotation, instructing Spring to only create Friend B when Friend A explicitly needs it. Although this might introduce a slight delay the first time Friend B is requested, it helps break the cycle. It's important to be cautious about potential issues, especially in scenarios involving multiple dependencies. While there's a more potent solution with spring.main.allow-circular-references, it comes with risks and is not always advisable. By employing @Lazy, we can ensure our friends cooperate without causing any undue trouble.

The better solution depends on the specific context and requirements of your application. Using @Lazy is a more straightforward and often safer approach, as it defers the creation of the bean until it's actually needed, breaking the circular dependency. On the other hand, enabling spring.main.allow-circular-references is a more advanced option that might be suitable for specific scenarios but comes with potential runtime issues. It's crucial to assess the trade-offs and choose the solution that aligns with the design and stability goals of your application.

Solution which worked well for me

@Autowired
@Lazy
private BeanB beanB;

(another silly note: using @RequiredArgsConstructor with @Lazy , won't work)

Upvotes: 4

Rutvik Jadhav
Rutvik Jadhav

Reputation: 31

if a class B is @Autowired in class A , this means you are directly injecting dependency of class B into that class A. If this causing cyclic dependency, then remove the @Autowired Annotation and create a constructor and add @Lazy annotation in the input parameter. The constructor will add the dependency whenever it is required .

class Dog{
    @Autowired 
    private Animal animal;
    .....
}

class Animal{
    .....
}

If this causing cyclic dependency, then

class Dog{
    private Animal animal;
    public Dog(@Lazy Animal animal){
    return this.animal=animal;
}

Upvotes: 1

hazartilirot
hazartilirot

Reputation: 355

I've just had the same issue with the code:

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {

    private final AuthenticationProvider authenticationProvider;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public WebSecurityConfiguration(
            AuthenticationProvider authenticationProvider,
            JwtAuthenticationFilter jwtAuthenticationFilter
    ) {
        this.authenticationProvider = authenticationProvider;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeHttpRequests()
                .requestMatchers(HttpMethod.POST, "/api/v1/users")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(
                        jwtAuthenticationFilter,
                        UsernamePasswordAuthenticationFilter.class
                );

        return http.build();
    }

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

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

    @Bean
    public AuthenticationProvider authenticationProvider(
            UserDetailsService userDetailsService,
            PasswordEncoder passwordEncoder
    ) {
        var daoAuthenticationProvider = new DaoAuthenticationProvider();

        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);

        return daoAuthenticationProvider;
    }

}

I then extracted the last three beans to a new class, with @Configuration annotation. It did the trick.

Upvotes: 1

Dancan Chibole
Dancan Chibole

Reputation: 171

So if you have a class A and you somehow run into this problem when injecting the class A into the constructor of class B, use the @Lazy annotation in the constructor of class B. This will break the cycle and inject the bean of A lazily into B. So, instead of fully initializing the bean, it will create a proxy to inject into the other bean. The injected bean will only be fully created when it's first needed.

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

Upvotes: 13

Junheng
Junheng

Reputation: 329

Have you upgraded your Spring Boot to 2.6.0 and later? Maybe you should modify your SecurityConfiguration. See this

In my project, I did this. Finially, it works well.

import com.yourweb.filter.JwtAuthenticationTokenFilter;
import com.yourweb.security.AuthenticationEntryPointImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Resource
    private AuthenticationEntryPointImpl authenticationEntryPoint;

    @Resource
    private LogoutSuccessHandler logoutSuccessHandler;

    @Resource
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    @Bean
    public AuthenticationManager authManager(
            HttpSecurity http,
            UserDetailsService userDetailsService,
            PasswordEncoder passwordEncoder,
            UserDetailsPasswordService userDetailsPasswordService) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder)
                .userDetailsPasswordManager(userDetailsPasswordService)
                .and()
                .build();
    }

    

    @Bean
    public PasswordEncoder passwordEncoder() {
        String idForEncode = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>(15);
        encoders.put(idForEncode, new BCryptPasswordEncoder());
        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf().disable()
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers("/auth/login", "/captcha").anonymous()
                .antMatchers(
                        HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
                .antMatchers("/profile/**").anonymous()
                .antMatchers("/upload/**").anonymous()
                .antMatchers("/common/download**").anonymous()
                .antMatchers("/swagger-ui/**").anonymous()
                .antMatchers("/swagger-resources/**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/*/api-docs").anonymous()
                .antMatchers("/druid/**").anonymous()
                .antMatchers("/modeler/**").anonymous()
                .antMatchers("/process/general/read-resource/**").anonymous()
                .antMatchers("/process/definition/resource/**").anonymous()
                .antMatchers("/activiti/getTracePhoto/**").anonymous()
                .antMatchers("/process/getTracePhoto/**").anonymous()
                .antMatchers("/**/deviceFileMaintenance/addDeviceFileMaintenance").anonymous()
                .antMatchers("/**/deviceFileInstall/addDeviceFileInstall").anonymous()
                .antMatchers("/**/photoUpload").anonymous()
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable()
                .and()
                .logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler)
                .and()
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}

Upvotes: 2

Arjun Gautam
Arjun Gautam

Reputation: 329

Instead of using Constructor Injection just use the @Autowired annotation. and also add the following line in resources/application.properties file

spring.main.allow-circular-references=true

For example :

Example for using @Autowired

Upvotes: 0

Domagoj Ratko
Domagoj Ratko

Reputation: 315

You can try with this. Add it to file application.properties

spring.main.allow-circular-references=true

And try to run. This is not best solution you still need to find better way to fix problem.

Upvotes: 18

Related Questions