javaspring-bootspring-securitypom.xmluserdetailsservice

Remember me button for login and BCrypt for passwords in Spring Boot 2.6.X and possibly Spring Boot 3


I made a very simple application in Spring Boot. I have used very few features offered by the framework and despite everything I have problems updating my application to the most recent versions of the framework. I also note that the support for Spring Security is disappointing because there is no dedicated community. As I wrote in the title, my needs are only 3:

  1. add a remember me button during login;
  2. use BCrypt to encrypt by password;
  3. use spring security on the most recent version of the framework and therefore 2.6.x and possibly also 3.0.

In the past I have opened a thread on this forum because the documentation claims that support for Spring Security is here on Stackoverflow but I have not found a solution to my problem.

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

It is disarming to learn that Spring Boot applications are not updatable and even more disarming that the Spring Security team is not present on Stackoverflow. My request is very simple, how can I extend WebSecurityConfigurerAdapter and how can I implement UserDetailsService to get what I need with 2.6.x? Also, I wouldn't mind replacing javax with jakarta and trying Spring Boot 3 on JDK 17 but if the support is non-existent, the code I find doesn't work and I have to read a 1000 page book every new version of the framework the advantage of using a framework is null. I am very disappointed, I hope that some Spring Security developer wishes to intervene. Below you will find the commented code (see points 1 and 2).

To make the application work and not have this problem:

Spring boot application fails to start after upgrading to 2.6.0 due to circular dependency[ unresolvable circular reference]

I have to use this code:

spring.main.allow-circular-references=true

But I don't want to use the above code, I want to update my application without using circular references.

Thank you

ConfigurazioneSpringSecurity extends WebSecurityConfigurerAdapter

// Configurazione di Spring Security.
@Configuration
@EnableWebSecurity
public class ConfigurazioneSpringSecurity extends WebSecurityConfigurerAdapter {

    // Metodi per la gestione dell'utente autenticato.
    @Autowired
    GestioneUtentiSpringSecurity gestioneUtentiSpringSecurity;
    
    // 1) Metodo per crittografare la password >> ?????????????
    @Bean
    public BCryptPasswordEncoder metodoCrittografia() {
        return new BCryptPasswordEncoder();
    }
    @Autowired
    public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
    }

    // 2.a) Pulsante "Ricordami" (l'utente non deve inserire ogni volta la password di login) >> ?????????????
    @Autowired
    private DataSource dataSource;
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
        db.setDataSource(dataSource);
        return db;
    }
    // Se l'utente cambia la password il pulsante "Ricordami" continua a funzionare.
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    // Configurazione di Spring Security.
    @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) + "')");
        // ... ecc...
        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");

        // 2.b) Pulsante "Ricordami" (l'utente non deve inserire ogni volta la password di login) >> ?????????????
        http.authorizeRequests().and()
                .rememberMe().tokenRepository(this.persistentTokenRepository())
                .tokenValiditySeconds(365 * 24 * 60 * 60);
    }

}

GestioneUtentiSpringSecurity implements UserDetailsService

// Creazione utente autenticato.
@Service
public class GestioneUtentiSpringSecurity implements UserDetailsService {

    @Autowired
    private UtenteRepository utenteRepository;
    @Autowired
    private RuoloRepository ruoloRepository;
    @Autowired
    //...

    // Creazione utente autenticato
    @Override
    public UserDetails loadUserByUsername(String nomeUtente) throws UsernameNotFoundException {

        // Si cerca l'utente nel database
        Utente utente = trovaUtenteConPrivilegiDiAutenticazione(nomeUtente);
        if (utente == null) {
            // System.out.println("L'utente " + nomeUtente + " non è stato trovato!");
            throw new UsernameNotFoundException("L'utente " + nomeUtente + " non è stato trovato nel database.");
        }
        // Si cercano i ruoli dell'utente
        List<String> ruoliUtente = null;
        try {
            ruoliUtente = this.ruoloRepository.trovaRuoliUtente(utente.getId());
        }catch (Exception b){
            ruoliUtente = null;
        }

        // Si caricano in una lista di oggetti GrantedAuthority i ruoli di un dato utente.
        // GrantedAuthority è una classe di Spring Security che contiene i privilegi di un utente.
        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;
        }

        // Si crea un oggetto specifico di Spring Security che rappresenta l'utente autenticato. Questo oggetto contiene 3
        // informazioni: nome utente, password e privilegi. Questi ultimi, in questa applicazione si fanno coincidere
        // con i ruoli.
        UserDetails userDetails = null;
        if((utente != null) && (ruoliUtente != null) && (grantList != null)){
            userDetails = (UserDetails) new User(utente.getNome(), utente.getPassword(), grantList);
        }
        return userDetails;
    }

    // Si cerca l'utente nel database
    public Utente trovaUtenteConPrivilegiDiAutenticazione(String nomeUtente){
        // ...
    }

}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!--/* cerca genitore dal repository */-->
    </parent>
    <groupId>...</groupId>
    <artifactId>...</artifactId>
    <version>...</version>
    <packaging>war</packaging>
    <name>...</name>
    <description>...</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-csv</artifactId>
            <version>1.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.1</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>gestioneutenti</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <configuration>
                        <packagingExcludes>
                            WEB-INF/classes/it/...
                        </packagingExcludes>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

Solution

  • Please try declaring the factory method of the password encoder static:

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

    This makes it clear to Spring that their is no actual dependency on other beans injected into ConfigurazioneSpringSecurity.

    This solves the problem at least in simpler setups: Spring boot 2.6.0 Error creating bean with name 'webSecurityConfig'

    It's hard to say for sure, as your code snippet is quite long and yet incomplete. If the suggestion above should not work, please consider providing the full log of the failed application start-up or a smaller yet complete sample that reproduces the problem.