我无法将我的 webapp 更新到 Spring Boot 2.6.0(2.5.7 可用,但 2.6.0 不可用)
I can't update my webapp to Spring Boot 2.6.0 (2.5.7 works but 2.6.0 doesn't)
如标题中所述,我无法将我的 webapp 更新到 Spring Boot 2.6.0。我使用 Spring Boot 2.5.5 编写了我的 webapp,一切正常。如果我用这个新标签更新 pom.xml 文件:
<version>2.5.7</version>
我的网络应用程序运行完美。所有测试都有效。
如果我执行此更新,webapp 不会启动:
<version>2.6.0</version>
启动 DEBUG 模式 IDE 向我显示一个错误和 2 个指向我的 webapp 的 2 classes 的链接。
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?
看来问题出在这个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();
}
}
或在此:
@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);
}
}
这些 class 有什么问题?
Spring Boot 2.6.0 有什么变化?
GestioneUtentiSpring安全实现 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;
}
}
}
从 Spring Boot 2.6 开始,默认禁止循环依赖。您可以通过设置以下 属性:
再次允许循环引用
spring.main.allow-circular-references = true
中阅读有关此内容的更多详细信息
我在迁移到 spring 启动 2 的过程中遇到了这个问题。6.x 使用 WebSecurityConfig
代码:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
return email -> {
....
};
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
...;
}
根据 WebSecurityConfigurerAdapter#userDetailsServiceBean
javadoc:
修复
覆盖此方法以公开从 configure(AuthenticationManagerBuilder)
创建的 UserDetailsService
作为 bean ...
要更改返回的实例,开发人员应更改 userDetailsService()
而不是
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())
...;
}
Spring这里面临的问题,导致无法从spring boot 2.6 默认配置spring.main.allow-circular-references = false
开始前进的问题位于以下部分
@Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
@Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
}
我相信这是因为 WebSecurityConfig extends WebSecurityConfigurerAdapter
在 class.
中有一些与 BCryptPasswordEncoder
结合的循环引用
解决方案是创建另一个配置 class,您可以在其中拆分配置,以便 spring 能够正确创建 bean,避免循环引用。
因此您可以创建以下额外内容class
@Configuration
public class CustomSecurityConfig {
@Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
}
然后在你原来的ConfigurazioneSpringSecurity.class
你可以替换失败
@Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
@Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
}
与
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity)
.passwordEncoder(passwordEncoder);
}
虽然设置 application.properties 有效,但它可能使用了将被弃用的功能。我能够通过使用基于 setter 的注入来解决这个问题。它有点冗长,但对于那些希望保持最新并且不使用可能会被弃用的功能的人来说可能是一个很好的起点。
这当然是一个可以改进的答案,我希望其他人可以提供更简洁的答案。如果我发现任何更清洁的东西,我会更新它。
之前
@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
}
}
之后
@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
}
}
如标题中所述,我无法将我的 webapp 更新到 Spring Boot 2.6.0。我使用 Spring Boot 2.5.5 编写了我的 webapp,一切正常。如果我用这个新标签更新 pom.xml 文件:
<version>2.5.7</version>
我的网络应用程序运行完美。所有测试都有效。 如果我执行此更新,webapp 不会启动:
<version>2.6.0</version>
启动 DEBUG 模式 IDE 向我显示一个错误和 2 个指向我的 webapp 的 2 classes 的链接。
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?
看来问题出在这个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();
}
}
或在此:
@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);
}
}
这些 class 有什么问题?
Spring Boot 2.6.0 有什么变化?
GestioneUtentiSpring安全实现 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;
}
}
}
从 Spring Boot 2.6 开始,默认禁止循环依赖。您可以通过设置以下 属性:
再次允许循环引用spring.main.allow-circular-references = true
中阅读有关此内容的更多详细信息
我在迁移到 spring 启动 2 的过程中遇到了这个问题。6.x 使用 WebSecurityConfig
代码:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
return email -> {
....
};
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
...;
}
根据 WebSecurityConfigurerAdapter#userDetailsServiceBean
javadoc:
覆盖此方法以公开从 configure(AuthenticationManagerBuilder)
创建的 UserDetailsService
作为 bean ...
要更改返回的实例,开发人员应更改 userDetailsService()
而不是
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())
...;
}
Spring这里面临的问题,导致无法从spring boot 2.6 默认配置spring.main.allow-circular-references = false
开始前进的问题位于以下部分
@Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
@Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
}
我相信这是因为 WebSecurityConfig extends WebSecurityConfigurerAdapter
在 class.
BCryptPasswordEncoder
结合的循环引用
解决方案是创建另一个配置 class,您可以在其中拆分配置,以便 spring 能够正确创建 bean,避免循环引用。
因此您可以创建以下额外内容class
@Configuration
public class CustomSecurityConfig {
@Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
}
然后在你原来的ConfigurazioneSpringSecurity.class
你可以替换失败
@Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
@Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
}
与
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity)
.passwordEncoder(passwordEncoder);
}
虽然设置 application.properties 有效,但它可能使用了将被弃用的功能。我能够通过使用基于 setter 的注入来解决这个问题。它有点冗长,但对于那些希望保持最新并且不使用可能会被弃用的功能的人来说可能是一个很好的起点。
这当然是一个可以改进的答案,我希望其他人可以提供更简洁的答案。如果我发现任何更清洁的东西,我会更新它。
之前
@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
}
}
之后
@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
}
}