Spring Boot 2.6.X 和 Spring Boot 3 中的“记住我”按钮用于登录和 BCrypt 用于密码

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

我在Spring Boot 中做了一个非常简单的应用程序。我很少使用该框架提供的功能,尽管如此,我还是无法将我的应用程序更新到该框架的最新版本。我还注意到对 Spring 安全性的支持令人失望,因为没有专门的社区。 正如我在标题中所写,我的需求只有3:

  1. 在登录时添加一个记住我的按钮;
  2. 使用BCrypt密码加密;
  3. 在最新版本的框架上使用 spring 安全性,因此 2.6.x 也可能是 3.0.

过去我在这个论坛上开了一个帖子,因为文档声称 Whosebug 上支持 Spring 安全,但我还没有找到解决我的问题的方法。

了解到 Spring 引导应用程序不可更新,这让人放下戒备,而 Spring 安全团队不在 Whosebug 上更让人放下心来。 我的要求很简单,如何扩展 WebSecurityConfigurerAdapter 以及如何实现 UserDetailsS​​ervice 以获得我需要的 2.6.x? 另外,我不介意用 jakarta 替换 javax 并在 JDK 17 上尝试 Spring Boot 3,但是如果支持是 non-existent,我发现的代码不起作用,我必须阅读一本 1000 页的书每一个新版本的框架使用框架的优势都是空的。我很失望,我希望一些 Spring 安全开发人员愿意介入。 您将在下面找到注释代码(参见第 1 点和第 2 点)。

要使应用程序正常运行且不会出现此问题:

我必须使用这个代码:

spring.main.allow-circular-references=true

但是我不想使用上面的代码,我想在不使用循环引用的情况下更新我的应用程序。

谢谢

ConfigurazioneSpring安全扩展 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);
    }

}

GestioneUtentiSpring安全实现 UserDetailsS​​ervice

// 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>

请尝试声明密码编码器的工厂方法static:

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

这让 Spring 清楚地知道它们实际上并不依赖于注入到 ConfigurazioneSpringSecurity 中的其他 bean。

这至少在更简单的设置中解决了问题:

很难确定,因为您的代码片段很长而且不完整。如果上述建议不起作用,请考虑提供失败应用程序的完整日志 start-up 或重现问题的较小但完整的示例。