Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security circular bean dependency

I'm currently working on a Vaadin spring application. According to the app specifications, authentication/authorization of the users must be completed by querying database via jdbcTemplate. How to solve this issue? I'm using Spring Boot 1.4.2.RELEASE.

UPDATE: This approach works with Spring Boot 1.1.x.RELEASE, however on the latest versions of it produces following error message.

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)

like image 681
Malakai Avatar asked Nov 19 '16 17:11

Malakai


People also ask

Can Spring handle circular dependency?

But with a circular dependency, Spring cannot decide which of the beans should be created first since they depend on one another. In these cases, Spring will raise a BeanCurrentlyInCreationException while loading context. It can happen in Spring when using constructor injection.

How do I fix circular dependency?

To resolve the circular dependency, you must break the loop by replacing the dynamic reference to the bucket resource.

What is circular dependency in Spring?

Circular dependency in Spring happens when two or more beans require instance of each other through constructor dependency injections. For example: There is a ClassA that requires an instance of ClassB through constructor injection and ClassB requires an instance of class A through constructor injection.

How do you avoid circular dependencies?

Avoiding circular dependencies by refactoring The NestJS documentation advises that circular dependencies be avoided where possible. Circular dependencies create tight couplings between the classes or modules involved, which means both classes or modules have to be recompiled every time either of them is changed.


1 Answers

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).

like image 50
dur Avatar answered Sep 20 '22 19:09

dur