I'm working on a Spring Boot application using Spring Security, and I've run into an issue with dependency injection in my AppConfig.java file. The application won't start unless I use the @Lazy annotation in the constructor. I'd prefer to avoid using @Lazy, but I'm stuck with circular reference errors when trying to configure the security without it.
Here's my current AppConfig.java:
@Configuration
@EnableWebSecurity
public class AppConfig extends WebSecurityConfigurerAdapter {
private final CurrentUserService currentUserService;
private final SessionFilter sessionFilter;
private final PasswordEncoder passwordEncoder;
@Autowired
@Lazy
public AppConfig(CurrentUserService currentUserService, SessionFilter sessionFilter, PasswordEncoder passwordEncoder) {
this.currentUserService = currentUserService;
this.sessionFilter = sessionFilter;
this.passwordEncoder = passwordEncoder;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(currentUserService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http = http.cors().and().csrf().disable();
http = http.exceptionHandling().authenticationEntryPoint(
(request, response, authException) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
).and();
http.authorizeRequests().antMatchers("/api/login").permitAll().anyRequest().authenticated();
http.addFilterBefore(sessionFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The Problem:
If I remove @Lazy, the application fails to start and throws the following error:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appConfig' defined in file [C:\path\to\AppConfig.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'appConfig': Requested bean is currently in creation: Is there an unresolvable circular reference?
I tried switching to field injection to avoid constructor injection, like this:
@Autowired
private CurrentUserService currentUserService;
@Autowired
private SessionFilter sessionFilter;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(currentUserService).passwordEncoder(passwordEncoder);
}
But I get a similar error:
Error creating bean with name 'appConfig': Unsatisfied dependency expressed through field 'passwordEncoder'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'appConfig': Requested bean is currently in creation: Is there an unresolvable circular reference?
My Questions:
How can I resolve this circular dependency without relying on @Lazy?
Is there a better approach for injecting these dependencies in a Spring Security configuration?
Are there specific patterns or workarounds for avoiding circular references in Spring Boot security setups?
Any advice or insights would be greatly appreciated!
This error occurs because you're creating the PasswordEncoder
in the same class that you're injecting it.
The best solution is to not autowire the PasswordEncoder
(or the CurrentUserService
) at all.
It appears those instances are only used in the configure(AuthenticationManagerBuilder auth)
method, which is redundant.
Registering a PasswordEncoder
and UserDetailsService
as a bean is enough for Spring Security to detect them and use them in your configuration.
In other words, you should remove the following code:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(currentUserService).passwordEncoder(passwordEncoder);
}
The application will still behave in the exact same way.