Spring 安全循环 bean 依赖
Spring Security circular bean dependency
我目前正在开发 Vaadin spring 应用程序。根据应用程序规范,authentication/authorization 的用户必须通过 jdbcTemplate
查询数据库来完成。如何解决这个问题?我正在使用 Spring Boot 1.4.2.RELEASE.
UPDATE:此方法适用于 Spring Boot 1。1.x.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)
└─────┘
原始代码如下所示:
账户资料库:
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
@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 如果将Spring Boot 版本降级到[1.1.5,1.2.0) ,就不会出现这个问题(由于其他依赖,我必须使用最新的)
您可以替换 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).
我更喜欢 @Lazy
方法。这样我就可以坚持一种模式。
您的 PasswordEncoder
bean 定义在 WebSecurityConfig
中,需要 JdbcUserDetailsServices
。 JdbcUserDetailsServices
再次依赖于需要 PasswordEncoder
的 JdbcAccountRepository
。这样就形成了循环。一个简单的解决方案是从 WebSecurityConfig
中取出 PasswordEncoder
bean 定义。即使在SecurityConfiguration
class里面也会解决循环问题
解决方案之一是不使用构造函数。例如代替:
private final JdbcTemplate jdbcTemplate;
private final PasswordEncoder passwordEncoder;
@Autowired
public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
this.jdbcTemplate = jdbcTemplate;
this.passwordEncoder = passwordEncoder;
}
您可以使用:
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private PasswordEncoder passwordEncoder;
我在一个class的构造函数中使用了@Lazy
,它解决了我的问题:
public class AService {
private BService b;
public ApplicantService(@NonNull @Lazy BService b) {
this.b = b;
}
}
public class BService {
private AService a;
public ApplicantService(@NonNull BService a) {
this.a = a;
}
}
@Zeeshan Adnan 是对的。从 WebSecurityConfig
中取出 PasswordEncoder
解决了 循环依赖问题 。
来自 Zeeshan 的回答:
Your PasswordEncoder bean definition is in WebSecurityConfig which needs JdbcUserDetailsServices. JdbcUserDetailsServices again is dependent on JdbcAccountRepository which needs PasswordEncoder. So the cycle forms. A simple solution is to take out the PasswordEncoder bean definition out of WebSecurityConfig. Even inside SecurityConfiguration class will solve the cyclic problem.
另一个简单的建议是将 PasswordEncoder 定义从 public 更改为 public 静态:
@Bean(name = "passwordEncoder")
public PasswordEncoder passwordencoder() {
return new CustomPasswordEncoder();
}
收件人:
@Bean(name = "passwordEncoder")
public static PasswordEncoder passwordencoder() {
return new CustomPasswordEncoder();
}
我目前正在开发 Vaadin spring 应用程序。根据应用程序规范,authentication/authorization 的用户必须通过 jdbcTemplate
查询数据库来完成。如何解决这个问题?我正在使用 Spring Boot 1.4.2.RELEASE.
UPDATE:此方法适用于 Spring Boot 1。1.x.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)
└─────┘
原始代码如下所示:
账户资料库:
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
@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 如果将Spring Boot 版本降级到[1.1.5,1.2.0) ,就不会出现这个问题(由于其他依赖,我必须使用最新的)
您可以替换 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).
我更喜欢 @Lazy
方法。这样我就可以坚持一种模式。
您的 PasswordEncoder
bean 定义在 WebSecurityConfig
中,需要 JdbcUserDetailsServices
。 JdbcUserDetailsServices
再次依赖于需要 PasswordEncoder
的 JdbcAccountRepository
。这样就形成了循环。一个简单的解决方案是从 WebSecurityConfig
中取出 PasswordEncoder
bean 定义。即使在SecurityConfiguration
class里面也会解决循环问题
解决方案之一是不使用构造函数。例如代替:
private final JdbcTemplate jdbcTemplate;
private final PasswordEncoder passwordEncoder;
@Autowired
public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
this.jdbcTemplate = jdbcTemplate;
this.passwordEncoder = passwordEncoder;
}
您可以使用:
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private PasswordEncoder passwordEncoder;
我在一个class的构造函数中使用了@Lazy
,它解决了我的问题:
public class AService {
private BService b;
public ApplicantService(@NonNull @Lazy BService b) {
this.b = b;
}
}
public class BService {
private AService a;
public ApplicantService(@NonNull BService a) {
this.a = a;
}
}
@Zeeshan Adnan 是对的。从 WebSecurityConfig
中取出 PasswordEncoder
解决了 循环依赖问题 。
来自 Zeeshan 的回答:
Your PasswordEncoder bean definition is in WebSecurityConfig which needs JdbcUserDetailsServices. JdbcUserDetailsServices again is dependent on JdbcAccountRepository which needs PasswordEncoder. So the cycle forms. A simple solution is to take out the PasswordEncoder bean definition out of WebSecurityConfig. Even inside SecurityConfiguration class will solve the cyclic problem.
另一个简单的建议是将 PasswordEncoder 定义从 public 更改为 public 静态:
@Bean(name = "passwordEncoder")
public PasswordEncoder passwordencoder() {
return new CustomPasswordEncoder();
}
收件人:
@Bean(name = "passwordEncoder")
public static PasswordEncoder passwordencoder() {
return new CustomPasswordEncoder();
}