Je travaille actuellement sur une application de printemps Vaadin. La seule chose que je peux dire, c'est que l'authentification/autorisation des utilisateurs doit se faire en interrogeant la base de données via jdbcTemplate
. Comment résoudre ce problème? J'utilise Spring Boot 1.4.2.RELEASE.
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| jdbcAccountRepository defined in file [repositories\JdbcAccountRepository.class]
↑ ↓
| securityConfiguration.WebSecurityConfig (field services.JdbcUserDetailsServicessecurity.SecurityConfiguration$WebSecurityConfig.userDetailsService)
↑ ↓
| jdbcUserDetailsServices (field repositories.JdbcAccountRepository services.JdbcUserDetailsServices.repository)
└─────┘
Le code d'origine ressemble à ceci:
AccountRepository:
public interface AccountRepository {
void createAccount(Account user) throws UsernameAlreadyInUseException;
Account findAccountByUsername(String username);
}
JdbcAccountRepository:
@Repository
public class JdbcAccountRepository implements AccountRepository {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
private final JdbcTemplate jdbcTemplate;
private final PasswordEncoder passwordEncoder;
@Autowired
public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
this.jdbcTemplate = jdbcTemplate;
this.passwordEncoder = passwordEncoder;
}
@Transactional
@Override
public void createAccount(Account user) throws UsernameAlreadyInUseException {
try {
jdbcTemplate.update(
"insert into Account (firstName, lastName, username, password, role) values (?, ?, ?, ?, ?)",
user.getFirstName(),
user.getLastName(),
user.getUsername(),
passwordEncoder.encode(
user.getPassword()),
user.getRole()
);
} catch (DuplicateKeyException e) {
throw new UsernameAlreadyInUseException(user.getUsername());
}
}
@Override
public Account findAccountByUsername(String username) {
return jdbcTemplate.queryForObject(
"select username, password, firstName, lastName, role from Account where username = ?",
(rs, rowNum) -> new Account(
rs.getString("username"),
rs.getString("password"),
rs.getString("firstName"),
rs.getString("lastName"),
rs.getString("role")),
username
);
}
}
JdbcUserDetailsServices:
@Service
public class JdbcUserDetailsServices implements UserDetailsService {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@Autowired
JdbcAccountRepository repository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
Account account = repository.findAccountByUsername(username);
User user = new User(
account.getUsername(),
account.getPassword(),
AuthorityUtils.createAuthorityList(
account.getRole()
)
);
return user;
} catch (DataAccessException e) {
LOGGER.debug("Account not found", e);
throw new UsernameNotFoundException("Username not found.");
}
}
}
Configuration de la sécurité:
@Configuration
@ComponentScan
public class SecurityConfiguration {
@Autowired
ApplicationContext context;
@Autowired
VaadinSecurity security;
@Bean
public PreAuthorizeSpringViewProviderAccessDelegate preAuthorizeSpringViewProviderAccessDelegate() {
return new PreAuthorizeSpringViewProviderAccessDelegate(security, context);
}
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public static class GlobalMethodSecurity extends GlobalMethodSecurityConfiguration {
@Bean
@Override
protected AccessDecisionManager accessDecisionManager() {
return super.accessDecisionManager();
}
}
@Configuration
@EnableWebSecurity
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JdbcUserDetailsServices userDetailsService;
@Autowired
DataSource dataSource;
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public TextEncryptor textEncryptor() {
return Encryptors.noOpText();
}
/*
* (non-Javadoc)
* @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
* #configure(org.springframework.security.config.annotation.web.builders.WebSecurity)
*/
@Override
public void configure(WebSecurity web) throws Exception {
//Ignoring static resources
web.ignoring().antMatchers("/VAADIN/**");
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService);
}
@Bean(name="authenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/*
* (non-Javadoc)
* @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
* #configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.and()
.csrf().disable();
}
}
}
P.S Si vous rétrogradez la version Spring Boot à [1.1.5,1.2.0), ce problème ne se produira pas (en raison d'autres dépendances, je dois utiliser la dernière)
Vous pouvez remplacer injection de dépendance basée sur le constructeur par injection de dépendance basée sur le setter pour résoudre le cycle, voir Spring Framework Reference Documentation :
Dépendances circulaires
Si vous utilisez principalement l'injection de constructeur, il est possible de créer un scénario de dépendance circulaire insoluble.
Par exemple: la classe A nécessite une instance de classe B par injection de constructeur, et la classe B nécessite une instance de classe A par injection de constructeur. Si vous configurez des beans pour que les classes A et B soient injectées l'une dans l'autre, le conteneur Spring IoC détecte cette référence circulaire au moment de l'exécution et lance un
BeanCurrentlyInCreationException
.Une solution possible consiste à modifier le code source de certaines classes à configurer par les setters plutôt que par les constructeurs. Sinon, évitez l'injection de constructeur et utilisez uniquement l'injection de setter. En d'autres termes, bien que cela ne soit pas recommandé, vous pouvez configurer des dépendances circulaires avec l'injection de setter.
Contrairement au cas typique (sans dépendances circulaires), une dépendance circulaire entre le haricot A et le haricot B force l'un des haricots à être injecté dans l'autre avant d'être complètement initialisé lui-même (scénario classique poulet/œuf).
Je préfère la méthode @Lazy. De cette façon, je peux m'en tenir à un modèle.
Voir http://www.baeldung.com/circular-dependencies-in-spring