问题
I'm currently working on a Vaadin spring application. The only thing I'm able to say is, authentication/authorization of users must be done by querying database via jdbcTemplate
. How to solve this issue? I'm using 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)
└─────┘
The original code looks like this:
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.");
}
}
}
SecurityConfiguration:
@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 If downgrade Spring Boot version to [1.1.5,1.2.0) , this problem will not occur ( due to other dependency, I must to use the latest)
回答1:
You could replace constructor-based dependency injection with setter-based dependency injection to resolve the cycle, see Spring Framework Reference Documentation:
Circular dependencies
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a
BeanCurrentlyInCreationException
.One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken/egg scenario).
回答2:
I prefer the @Lazy method. That way I can stick to one pattern.
See http://www.baeldung.com/circular-dependencies-in-spring
来源:https://stackoverflow.com/questions/40695893/spring-security-circular-bean-dependency