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:
- 在登录时添加一个记住我的按钮;
- 使用BCrypt密码加密;
- 在最新版本的框架上使用 spring 安全性,因此 2.6.x 也可能是 3.0.
过去我在这个论坛上开了一个帖子,因为文档声称 Whosebug 上支持 Spring 安全,但我还没有找到解决我的问题的方法。
了解到 Spring 引导应用程序不可更新,这让人放下戒备,而 Spring 安全团队不在 Whosebug 上更让人放下心来。
我的要求很简单,如何扩展 WebSecurityConfigurerAdapter 以及如何实现 UserDetailsService 以获得我需要的 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安全实现 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>
请尝试声明密码编码器的工厂方法static
:
@Bean
public static BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
这让 Spring 清楚地知道它们实际上并不依赖于注入到 ConfigurazioneSpringSecurity
中的其他 bean。
这至少在更简单的设置中解决了问题:
很难确定,因为您的代码片段很长而且不完整。如果上述建议不起作用,请考虑提供失败应用程序的完整日志 start-up 或重现问题的较小但完整的示例。
我在Spring Boot 中做了一个非常简单的应用程序。我很少使用该框架提供的功能,尽管如此,我还是无法将我的应用程序更新到该框架的最新版本。我还注意到对 Spring 安全性的支持令人失望,因为没有专门的社区。 正如我在标题中所写,我的需求只有3:
- 在登录时添加一个记住我的按钮;
- 使用BCrypt密码加密;
- 在最新版本的框架上使用 spring 安全性,因此 2.6.x 也可能是 3.0.
过去我在这个论坛上开了一个帖子,因为文档声称 Whosebug 上支持 Spring 安全,但我还没有找到解决我的问题的方法。
了解到 Spring 引导应用程序不可更新,这让人放下戒备,而 Spring 安全团队不在 Whosebug 上更让人放下心来。 我的要求很简单,如何扩展 WebSecurityConfigurerAdapter 以及如何实现 UserDetailsService 以获得我需要的 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安全实现 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>
请尝试声明密码编码器的工厂方法static
:
@Bean
public static BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
这让 Spring 清楚地知道它们实际上并不依赖于注入到 ConfigurazioneSpringSecurity
中的其他 bean。
这至少在更简单的设置中解决了问题:
很难确定,因为您的代码片段很长而且不完整。如果上述建议不起作用,请考虑提供失败应用程序的完整日志 start-up 或重现问题的较小但完整的示例。