'I can't update my webapp to Spring Boot 2.6.0 (2.5.7 works but 2.6.0 doesn't)

As mentioned in the title I can't update my webapp to Spring Boot 2.6.0. I wrote my webapp using Spring Boot 2.5.5 and everything works perfectly. If I update the pom.xml file with this new tag:

<version>2.5.7</version>

My webapp works perfectly. All tests work. If I perform this update the webapp does not start:

<version>2.6.0</version>

Starting the DEBUG mode the IDE shows me an error and 2 links to 2 classes of my webapp.

2021-11-23 00:31:45.419 ERROR 21884 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'configurazioneSpringSecurity': Requested bean is currently in creation: Is there an unresolvable circular reference?

It seems the problem is in this class:

@Configuration
@EnableWebSecurity
public class ConfigurazioneSpringSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    LivelliDeiRuoli livelliDeiRuoli;

    @Autowired
    GestioneUtentiSpringSecurity gestioneUtentiSpringSecurity;

    @Bean
    public BCryptPasswordEncoder metodoCrittografia() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();

        http.authorizeRequests().antMatchers(
                "/",
                "/login",
                "/benvenuto",
                "/registrazione",
                "/registrazione-eseguita",
                "/pagine-applicazione"
        ).permitAll();

        http.authorizeRequests().antMatchers("/area-riservata")
                .access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(1L) + "')");

        http.authorizeRequests().antMatchers("/cambio-password")
                .access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(1L) + "')");

        http.authorizeRequests().antMatchers("/cambio-nome")
                .access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(1L) + "')");

        http.authorizeRequests().antMatchers("/cancella-utente")
                .access("isAuthenticated()");

        http.authorizeRequests().antMatchers("/gestione-utenti")
                .access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(2L) + "')");

        http.authorizeRequests().antMatchers("/gestione-ruoli")
                .access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(3L) + "')");

        http.authorizeRequests().antMatchers("/pannello-di-controllo")
                .access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(3L) + "')");

        http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/errore-403");

        http.authorizeRequests().and().formLogin()
                .loginProcessingUrl("/pagina-login")
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .failureUrl("/login?errore=true")
                .usernameParameter("username")
                .passwordParameter("password")
                .and().logout().logoutUrl("/pagina-logout")
                .logoutSuccessUrl("/login?logout=true");

        http.authorizeRequests().and() //
                .rememberMe().tokenRepository(this.persistentTokenRepository()) //
                .tokenValiditySeconds(365 * 24 * 60 * 60);
                
        http.authorizeRequests().antMatchers("/gestione-eventi")
                .access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(2L) + "')");

        http.authorizeRequests().antMatchers(
                "/cerca-eventi",
                "/ultimi-eventi"
        ).permitAll();

    }

    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
        db.setDataSource(dataSource);
        return db;
    }

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

or in this:

@SpringBootApplication
@Profile("sviluppo")
public class GestioneUtentiApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(GestioneUtentiApplication.class);
    }

    public static void main(String[] args) {
        System.setProperty("server.servlet.context-path", "/gestioneutenti");
        SpringApplication.run(GestioneUtentiApplication.class, args);
    }

}

What's wrong with these classes?

What changes with Spring Boot 2.6.0?

GestioneUtentiSpringSecurity implements UserDetailsService:

@Service
public class GestioneUtentiSpringSecurity implements UserDetailsService {

    @Autowired
    private UtenteRepository utenteRepository;

    @Autowired
    private RuoloRepository ruoloRepository;

    @Autowired
    EseguiVariabiliDiSistema eseguiVariabiliDiSistema;
    
    @Autowired
    LivelliDeiRuoli livelliDeiRuoli;

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

        Utente utente = trovaUtenteConPrivilegiDiAutenticazione(nomeUtente);

        if (utente == null) {
            throw new UsernameNotFoundException("L'utente " + nomeUtente + " non è stato trovato nel database.");
        }

        List<String> ruoliUtente = null;
        try {
            ruoliUtente = this.ruoloRepository.trovaRuoliUtente(utente.getId());
        }catch (Exception b){
            ruoliUtente = null;
        }

        List<GrantedAuthority> grantList = null;
        try{
            grantList = new ArrayList<GrantedAuthority>();
            if (ruoliUtente != null) {
                for (String ruolo : ruoliUtente) {
                    GrantedAuthority authority = new SimpleGrantedAuthority(ruolo);
                    grantList.add(authority);
                }
            }
        }catch (Exception c){
            grantList = null;
        }

        UserDetails userDetails = null;
        if((utente != null) && (ruoliUtente != null) && (grantList != null)){
            userDetails = (UserDetails) new User(utente.getNome(), utente.getPassword(), grantList);
        }
        return userDetails;
    }

    public Utente trovaUtenteConPrivilegiDiAutenticazione(String nomeUtente){
        try{
            Utente utente = utenteRepository.trovaUtente(nomeUtente);
            if(livelliDeiRuoli.requisitiUtenteConRuoloMassimo(utente)){
                return utente;
            } else{
                eseguiVariabiliDiSistema.trovaVariabileSenzaVerificaUtente(
                        new VariabileSistema(0L, "login", "")
                );
                if(eseguiVariabiliDiSistema.getVariabileDiSistema().getValore().equals("true")){
                    return utente;
                }else if(eseguiVariabiliDiSistema.getVariabileDiSistema().getValore().equals("false")){
                    return null;
                }else{
                    return null;
                }
            }
        }catch (Exception e){
            return null;
        }
    }

}


Solution 1:[1]

Starting on Spring Boot 2.6, circular dependencies are prohibited by default. you can allow circular references again by setting the following property:

spring.main.allow-circular-references = true

You can read some more details about this in the Spring Boot 2.6 Release Notes.

Solution 2:[2]

The problem that Spring faces here and causes to not able to move forward starting from spring boot 2.6 with the default configuration of spring.main.allow-circular-references = false is located in the following part

    @Bean
    public BCryptPasswordEncoder metodoCrittografia() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
    }

I believe this is happening because the WebSecurityConfig extends WebSecurityConfigurerAdapter has some circular references in combination with BCryptPasswordEncoder inside this class.

The solution is to create another configuration class, where you can split the configurations so that spring is able to correctly create the beans avoiding circular references.

So you can create the following extra class

@Configuration
public class CustomSecurityConfig {
    
   @Bean
   public BCryptPasswordEncoder metodoCrittografia() {
      return new BCryptPasswordEncoder();
   }
}

Then in your original ConfigurazioneSpringSecurity.class you can replace the failing

    @Bean
    public BCryptPasswordEncoder metodoCrittografia() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
    }

with the

    @Autowired
    private PasswordEncoder passwordEncoder;
 
    @Autowired
    public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
     
        auth.userDetailsService(gestioneUtentiSpringSecurity)
                            .passwordEncoder(passwordEncoder);
        }

Solution 3:[3]

Although setting the application.properties works, it is likely using a feature that is going to be deprecated. I was able to work around this by using setter based injection. It's a bit more verbose but might be a good starting point for those looking to stay current and not use features that might be deprecated down the line.

It's certainly an answer that can be improved upon and I hope others can contribute perhaps more concise answers. I'll update this if I find anything cleaner.

Before

@Component
public class CustomFilter extends OncePerRequestFilter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    private JWTUtils jwtUtils;

    //When any api will be called this method will be called first and this will extract
    // Token from header pass to JWT Util calls for token details extraction
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse, FilterChain filterChain)
            throws ServletException, IOException {
        //implementation
    }
}

After

@Component
public class CustomFilter extends OncePerRequestFilter {

    private MyUserDetailsService myUserDetailsService;

    public void setMyUserDetailsService(MyUserDetailsService myUserDetailsService) {
        this.myUserDetailsService = myUserDetailsService;
    }

    public void setJwtUtils(JWTUtils jwtUtils) {
        this.jwtUtils = jwtUtils;
    }

    private JWTUtils jwtUtils;

    //When any api will be called this method will be called first and this will extract
    // Token from header pass to JWT Util calls for token details extraction
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse, FilterChain filterChain)
            throws ServletException, IOException {

       //implementation
    }
}

reference: https://theintuitiveprogrammer.com/post-eight.html

Solution 4:[4]

I've this problem during migrate to spring boot 2.6.x with WebSecurityConfig code:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Bean 
  public UserDetailsService userDetailsService() {
    return email -> {
          ....
    };
  }

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService())
            ...;
  }

fix according WebSecurityConfigurerAdapter#userDetailsServiceBean javadoc:

Override this method to expose a UserDetailsService created from configure(AuthenticationManagerBuilder) as a bean ... To change the instance returned, developers should change userDetailsService() instead

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Bean
  @Override
  public UserDetailsService userDetailsServiceBean() throws Exception {
    return super.userDetailsServiceBean();
  }

  @Override
  public UserDetailsService userDetailsService() {
    return email -> {
          ....
    };
  }

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService())
            ...;
  }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 João Dias
Solution 2 Panagiotis Bougioukos
Solution 3 amuniz
Solution 4